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

lballabio / QuantLib / 25567645370

08 May 2026 04:42PM UTC coverage: 74.511% (+0.06%) from 74.45%
25567645370

push

github

web-flow
Add term structure to G2 processes (#2555)

48 of 63 new or added lines in 1 file covered. (76.19%)

4 existing lines in 2 files now uncovered.

58376 of 78346 relevant lines covered (74.51%)

8675879.06 hits per line

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

48.75
/ql/processes/g2process.cpp
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,
8✔
26
                         const Handle<YieldTermStructure>& termStructure)
8✔
27
    : a_(a), sigma_(sigma), b_(b), eta_(eta), rho_(rho),
8✔
28
      xProcess_(new QuantLib::OrnsteinUhlenbeckProcess(a, sigma, 0.0)),
8✔
29
      yProcess_(new QuantLib::OrnsteinUhlenbeckProcess(b, eta, 0.0)),
8✔
30
      termStructure_(termStructure) {
8✔
31
        registerWith(termStructure_);
8✔
32
    }
8✔
33

34
    Size G2Process::size() const {
40,003✔
35
        return 2;
40,003✔
36
    }
37

38
    Array G2Process::initialValues() const {
20,003✔
39
        Real z1_0 = termStructure_.empty() ? x0_ : phi(0.0);
20,003✔
40
        return { z1_0, y0_ };
20,003✔
41
    }
42

43
    Array G2Process::drift(Time t, const Array& z) const {
2✔
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
        Real shiftDrift = 0.0;
48
        if (!termStructure_.empty()) {
2✔
49
            const Real h = 1.0e-4;
50
            Real phi_t  = phi(t);
1✔
51
            Real phi_th = phi(t + h);
1✔
52
            Real phiPrime = (phi_th - phi_t) / h;
1✔
53
            shiftDrift = a_ * phi_t + phiPrime;
1✔
54
        }
55
        return {
56
            xProcess_->drift(t, z[0]) + shiftDrift,
2✔
57
            yProcess_->drift(t, z[1])
2✔
58
        };
4✔
59
    }
60

61
    Matrix G2Process::diffusion(Time, const Array&) const {
2✔
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
        Matrix tmp(2,2);
70
        Real sigma1 = sigma_;
2✔
71
        Real sigma2 = eta_;
2✔
72
        tmp[0][0] = sigma1;       tmp[0][1] = 0.0;
2✔
73
        tmp[1][0] = rho_*sigma1;  tmp[1][1] = std::sqrt(1.0-rho_*rho_)*sigma2;
2✔
74
        return tmp;
2✔
75
    }
76

77
    Array G2Process::expectation(Time t0, const Array& z0,
1,000,005✔
78
                                 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
        Real shiftExp = 0.0;
83
        if (!termStructure_.empty()) {
1,000,005✔
84
            shiftExp = phi(t0 + dt) - phi(t0) * std::exp(-a_ * dt);
1,000,005✔
85
        }
86
        return {
87
            xProcess_->expectation(t0, z0[0], dt) + shiftExp,
1,000,005✔
88
            yProcess_->expectation(t0, z0[1], dt)
1,000,005✔
89
        };
2,000,010✔
90
    }
91

92
    Matrix G2Process::stdDeviation(Time t0, const Array& x0, Time dt) const {
1,000,000✔
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
        Matrix tmp(2,2);
101
        Real sigma1 = xProcess_->stdDeviation(t0, x0[0], dt);
1,000,000✔
102
        Real sigma2 = yProcess_->stdDeviation(t0, x0[1], dt);
1,000,000✔
103
        Real expa = std::exp(-a_*dt), expb = std::exp(-b_*dt);
1,000,000✔
104
        Real H = (rho_*sigma_*eta_)/(a_+b_)*(1-expa*expb);
1,000,000✔
105
        Real den =
106
            (0.5*sigma_*eta_)*std::sqrt((1-expa*expa)*(1-expb*expb)/(a_*b_));
1,000,000✔
107
        Real newRho = H/den;
1,000,000✔
108
        tmp[0][0] = sigma1;
1,000,000✔
109
        tmp[0][1] = 0.0;
1,000,000✔
110
        tmp[1][0] = newRho*sigma2;
1,000,000✔
111
        tmp[1][1] = std::sqrt(1.0-newRho*newRho)*sigma2;
1,000,000✔
112
        return tmp;
1,000,000✔
113
    }
114

115
    Matrix G2Process::covariance(Time t0, const Array& x0, Time dt) const {
×
116
        Matrix sigma = stdDeviation(t0, x0, dt);
×
117
        Matrix result = sigma*transpose(sigma);
×
118
        return result;
×
119
    }
120

121
    Real G2Process::x0() const {
1✔
122
        return termStructure_.empty() ? x0_ : phi(0.0);
1✔
123
    }
124

125
    Real G2Process::y0() const {
1✔
126
        return y0_;
1✔
127
    }
128

129
    Real G2Process::a() const {
×
130
        return a_;
×
131
    }
132

133
    Real G2Process::sigma() const {
×
134
        return sigma_;
×
135
    }
136

137
    Real G2Process::b() const {
×
138
        return b_;
×
139
    }
140

141
    Real G2Process::eta() const {
×
142
        return eta_;
×
143
    }
144

145
    Real G2Process::rho() const {
×
146
        return rho_;
×
147
    }
148

149
    const Handle<YieldTermStructure>& G2Process::termStructure() const {
1✔
150
        return termStructure_;
1✔
151
    }
152

153
    Real G2Process::phi(Time t) const {
2,020,085✔
154
        QL_REQUIRE(!termStructure_.empty(),
2,020,087✔
155
                   "no term structure given to G2Process");
156
        Rate forward = termStructure_->forwardRate(t, t, Continuous, NoFrequency);
2,020,084✔
157
        Real temp1 = sigma_*(1.0-std::exp(-a_*t))/a_;
2,020,084✔
158
        Real temp2 = eta_ *(1.0-std::exp(-b_*t))/b_;
2,020,084✔
159
        return 0.5*temp1*temp1 + 0.5*temp2*temp2 + rho_*temp1*temp2 + forward;
2,020,084✔
160
    }
161

162
    Rate G2Process::shortRate(Time, Real z1, Real z2) const {
11✔
163
        // The simulated state already includes phi(t) in z1, so r = z1 + z2.
164
        return z1 + z2;
11✔
165
    }
166

167

168
    G2ForwardProcess::G2ForwardProcess(Real a, Real sigma, Real b, Real eta, Real rho,
2✔
169
                                       const Handle<YieldTermStructure>& termStructure)
2✔
170
    : a_(a), sigma_(sigma), b_(b), eta_(eta), rho_(rho),
2✔
171
      xProcess_(new QuantLib::OrnsteinUhlenbeckProcess(a, sigma, 0.0)),
2✔
172
      yProcess_(new QuantLib::OrnsteinUhlenbeckProcess(b, eta, 0.0)),
2✔
173
      termStructure_(termStructure) {
2✔
174
        registerWith(termStructure_);
2✔
175
    }
2✔
176

177
    Size G2ForwardProcess::size() const {
×
178
        return 2;
×
179
    }
180

181
    Array G2ForwardProcess::initialValues() const {
×
NEW
182
        Real z1_0 = termStructure_.empty() ? x0_ : phi(0.0);
×
NEW
183
        return { z1_0, y0_ };
×
184
    }
185

NEW
186
    Array G2ForwardProcess::drift(Time t, const Array& z) const {
×
187
        Real shiftDrift = 0.0;
NEW
188
        if (!termStructure_.empty()) {
×
189
            const Real h = 1.0e-4;
NEW
190
            Real phi_t  = phi(t);
×
NEW
191
            Real phi_th = phi(t + h);
×
NEW
192
            Real phiPrime = (phi_th - phi_t) / h;
×
NEW
193
            shiftDrift = a_ * phi_t + phiPrime;
×
194
        }
195
        return {
NEW
196
            xProcess_->drift(t, z[0]) + xForwardDrift(t, T_) + shiftDrift,
×
NEW
197
            yProcess_->drift(t, z[1]) + yForwardDrift(t, T_)
×
UNCOV
198
        };
×
199
    }
200

201
    Matrix G2ForwardProcess::diffusion(Time, const Array&) const {
×
202
        Matrix tmp(2,2);
203
        Real sigma1 = sigma_;
×
204
        Real sigma2 = eta_;
×
205
        tmp[0][0] = sigma1;       tmp[0][1] = 0.0;
×
206
        tmp[1][0] = rho_*sigma1;  tmp[1][1] = std::sqrt(1.0-rho_*rho_)*sigma2;
×
207
        return tmp;
×
208
    }
209

NEW
210
    Array G2ForwardProcess::expectation(Time t0, const Array& z0,
×
211
                                        Time dt) const {
212
        Real shiftExp = 0.0;
NEW
213
        if (!termStructure_.empty()) {
×
NEW
214
            shiftExp = phi(t0 + dt) - phi(t0) * std::exp(-a_ * dt);
×
215
        }
216
        return {
NEW
217
            xProcess_->expectation(t0, z0[0], dt) - Mx_T(t0, t0+dt, T_) + shiftExp,
×
NEW
218
            yProcess_->expectation(t0, z0[1], dt) - My_T(t0, t0+dt, T_)
×
UNCOV
219
        };
×
220
    }
221

222
    Matrix G2ForwardProcess::stdDeviation(Time t0, const Array& x0, Time dt) const {
×
223
        Matrix tmp(2,2);
224
        Real sigma1 = xProcess_->stdDeviation(t0, x0[0], dt);
×
225
        Real sigma2 = yProcess_->stdDeviation(t0, x0[1], dt);
×
226
        Real expa = std::exp(-a_*dt), expb = std::exp(-b_*dt);
×
227
        Real H = (rho_*sigma_*eta_)/(a_+b_)*(1-expa*expb);
×
228
        Real den =
229
            (0.5*sigma_*eta_)*std::sqrt((1-expa*expa)*(1-expb*expb)/(a_*b_));
×
230
        Real newRho = H/den;
×
UNCOV
231
        tmp[0][0] = sigma1;
×
232
        tmp[0][1] = 0.0;
×
233
        tmp[1][0] = newRho*sigma2;
×
234
        tmp[1][1] = std::sqrt(1.0-newRho*newRho)*sigma2;
×
235
        return tmp;
×
236
    }
237

238
    Matrix G2ForwardProcess::covariance(Time t0, const Array& x0, Time dt) const {
×
239
        Matrix sigma = stdDeviation(t0, x0, dt);
×
240
        Matrix result = sigma*transpose(sigma);
×
241
        return result;
×
242
    }
243

244
    Real G2ForwardProcess::xForwardDrift(Time t, Time T) const {
×
245
        Real expatT = std::exp(-a_*(T-t));
×
246
        Real expbtT = std::exp(-b_*(T-t));
×
247

248
        return -(sigma_*sigma_/a_) * (1-expatT)
×
249
              - (rho_*sigma_*eta_/b_) * (1-expbtT);
×
250
    }
251

252
    Real G2ForwardProcess::yForwardDrift(Time t, Time T) const {
×
253
        Real expatT = std::exp(-a_*(T-t));
×
254
        Real expbtT = std::exp(-b_*(T-t));
×
255

256
        return -(eta_*eta_/b_) * (1-expbtT)
×
257
              - (rho_*sigma_*eta_/a_) * (1-expatT);
×
258
    }
259

260
    Real G2ForwardProcess::Mx_T(Real s, Real t, Real T) const {
×
261
        Real M;
262
        M = ( (sigma_*sigma_)/(a_*a_) + (rho_*sigma_*eta_)/(a_*b_) )
×
263
          * (1-std::exp(-a_*(t-s)));
×
264
        M += -(sigma_*sigma_)/(2*a_*a_) *
×
265
              (std::exp(-a_*(T-t))-std::exp(-a_*(T+t-2*s)));
×
266
        M += -(rho_*sigma_*eta_)/(b_*(a_+b_))
×
267
            * (std::exp(-b_*(T-t)) -std::exp(-b_*T-a_*t+(a_+b_)*s));
×
268
        return M;
×
269
    }
270

271
    Real G2ForwardProcess::My_T(Real s, Real t, Real T) const {
×
272
        Real M;
273
        M = ( (eta_*eta_)/(b_*b_) + (rho_*sigma_*eta_)/(a_*b_) )
×
274
          * (1-std::exp(-b_*(t-s)));
×
275
        M += -(eta_*eta_)/(2*b_*b_) *
×
276
              (std::exp(-b_*(T-t))-std::exp(-b_*(T+t-2*s)));
×
277
        M += -(rho_*sigma_*eta_)/(a_*(a_+b_))
×
278
            * (std::exp(-a_*(T-t))-std::exp(-a_*T-b_*t+(a_+b_)*s));
×
279
        return M;
×
280
    }
281

282
    const Handle<YieldTermStructure>& G2ForwardProcess::termStructure() const {
1✔
283
        return termStructure_;
1✔
284
    }
285

286
    Real G2ForwardProcess::phi(Time t) const {
4✔
287
        QL_REQUIRE(!termStructure_.empty(),
6✔
288
                   "no term structure given to G2ForwardProcess");
289
        Rate forward = termStructure_->forwardRate(t, t, Continuous, NoFrequency);
3✔
290
        Real temp1 = sigma_*(1.0-std::exp(-a_*t))/a_;
3✔
291
        Real temp2 = eta_ *(1.0-std::exp(-b_*t))/b_;
3✔
292
        return 0.5*temp1*temp1 + 0.5*temp2*temp2 + rho_*temp1*temp2 + forward;
3✔
293
    }
294

295
    Rate G2ForwardProcess::shortRate(Time, Real z1, Real z2) const {
4✔
296
        return z1 + z2;
4✔
297
    }
298

299
}
300

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

© 2026 Coveralls, Inc