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

lballabio / QuantLib / 21247173144

22 Jan 2026 11:44AM UTC coverage: 74.184% (+0.01%) from 74.172%
21247173144

Pull #2414

github

web-flow
Merge d5c127c70 into 71e800be7
Pull Request #2414: Add FX Forward instrument, engine, and tests

102 of 111 new or added lines in 3 files covered. (91.89%)

345 existing lines in 25 files now uncovered.

57443 of 77433 relevant lines covered (74.18%)

8775180.22 hits per line

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

93.68
/ql/processes/gsrprocesscore.cpp
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2

3
/*
4
 Copyright (C) 2015 Peter Caspers
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/gsrprocesscore.hpp>
21
#include <cmath>
22

23
using std::exp;
24
using std::pow;
25

26
namespace QuantLib::detail {
27

28
GsrProcessCore::GsrProcessCore(Array times, Array vols,
10✔
29
                               Array reversions, const Real T)
10✔
30
    : times_(std::move(times)), vols_(std::move(vols)), reversions_(std::move(reversions)),
31
      T_(T), revZero_(reversions_.size(), false) {
10✔
32
    flushCache();
10✔
33
    checkTimesVolsReversions();
10✔
34
}
10✔
35

36
void GsrProcessCore::setTimes(Array times) {
3✔
37
    times_ = std::move(times);
38
    checkTimesVolsReversions();
3✔
39
}
3✔
40

41
void GsrProcessCore::setVols(Array vols) {
607✔
42
    vols_ = std::move(vols);
43
    checkTimesVolsReversions();
607✔
44
}
607✔
45

46
void GsrProcessCore::setReversions(Array reversions) {
607✔
47
    reversions_ = std::move(reversions);
48
    checkTimesVolsReversions();
607✔
49
}
607✔
50

51
void GsrProcessCore::checkTimesVolsReversions() const {
1,227✔
52
    QL_REQUIRE(times_.size() == vols_.size() - 1,
1,227✔
53
               "number of volatilities ("
54
                   << vols_.size() << ") compared to number of times ("
55
                   << times_.size() << " must be bigger by one");
56
    QL_REQUIRE(times_.size() == reversions_.size() - 1 || reversions_.size() == 1,
1,227✔
57
               "number of reversions ("
58
                   << vols_.size() << ") compared to number of times ("
59
                   << times_.size() << " must be bigger by one, or exactly "
60
                                       "1 reversion must be given");
61
    for (int i = 0; i < ((int)times_.size()) - 1; i++)
10,030✔
62
        QL_REQUIRE(times_[i] < times_[i + 1], "times must be increasing ("
8,803✔
63
                                                << times_[i] << "@" << i << " , "
64
                                                << times_[i + 1] << "@" << i + 1
65
                                                << ")");
66
}
1,227✔
67

68
void GsrProcessCore::flushCache() const {
631✔
69
    for (int i = 0; i < (int)reversions_.size(); i++)
1,799✔
70
        // small reversions cause numerical problems, so we keep them
71
        // away from zero
72
        if (std::fabs(reversions_[i]) < 1E-4)
1,168✔
73
            revZero_[i] = true;
74
        else
75
            revZero_[i] = false;
76
    cache1_.clear();
77
    cache2a_.clear();
78
    cache2b_.clear();
79
    cache3_.clear();
80
    cache4_.clear();
81
    cache5_.clear();
82
}
631✔
83

84
Real GsrProcessCore::expectation_x0dep_part(const Time w, const Real xw,
4,306,693✔
85
                                            const Time dt) const {
86
    Real t = w + dt;
4,306,693✔
87
    std::pair<Real, Real> key;
88
    key = std::make_pair(w, t);
89
    auto k = cache1_.find(key);
90
    if (k != cache1_.end())
4,306,693✔
91
        return xw * (k->second);
4,304,926✔
92
    // A(w,t)x(w)
93
    Real res2 = 1.0;
94
    for (int i = lowerIndex(w); i <= upperIndex(t) - 1; i++) {
10,126✔
95
        res2 *= exp(-rev(i) * (cappedTime(i + 1, t) - flooredTime(i, w)));
8,359✔
96
    }
97
    cache1_.insert(std::make_pair(key, res2));
1,767✔
98
    return res2 * xw;
1,767✔
99
}
100

101
Real GsrProcessCore::expectation_rn_part(const Time w,
4,306,693✔
102
                                         const Time dt) const {
103

104
    Real t = w + dt;
4,306,693✔
105

106
    std::pair<Real, Real> key;
107
    key = std::make_pair(w, t);
108
    auto k =
109
        cache2a_.find(key);
110
    if (k != cache2a_.end())
4,306,693✔
111
        return k->second;
4,304,926✔
112

113
    Real res = 0.0;
114

115
    // \int A(s,t)y(s)
116
    for (int k = lowerIndex(w); k <= upperIndex(t) - 1; k++) {
10,126✔
117
        // l<k
118
        for (int l = 0; l <= k - 1; l++) {
97,778✔
119
            Real res2 = 1.0;
120
            // alpha_l
121
            res2 *= revZero(l) ? Real(vol(l) * vol(l) * (time2(l + 1) - time2(l)))
89,419✔
122
                               : vol(l) * vol(l) / (2.0 * rev(l)) *
89,419✔
123
                                     (1.0 - exp(-2.0 * rev(l) *
89,419✔
124
                                                (time2(l + 1) - time2(l))));
89,419✔
125
            // zeta_i (i>k)
126
            for (int i = k + 1; i <= upperIndex(t) - 1; i++)
945,423✔
127
                res2 *= exp(-rev(i) * (cappedTime(i + 1, t) - time2(i)));
856,004✔
128
            // beta_j (j<k)
129
            for (int j = l + 1; j <= k - 1; j++)
1,206,758✔
130
                res2 *= exp(-2.0 * rev(j) * (time2(j + 1) - time2(j)));
1,117,339✔
131
            // zeta_k beta_k
132
            res2 *=
89,419✔
133
                revZero(k)
89,419✔
134
                    ? Real(2.0 * time2(k) - flooredTime(k, w) -
89,419✔
135
                          cappedTime(k + 1, t) -
×
136
                          2.0 * (time2(k) - cappedTime(k + 1, t)))
×
137
                    : Real((exp(rev(k) * (2.0 * time2(k) - flooredTime(k, w) -
89,419✔
138
                                     cappedTime(k + 1, t))) -
89,419✔
139
                       exp(2.0 * rev(k) * (time2(k) - cappedTime(k + 1, t)))) /
89,419✔
140
                          rev(k));
89,419✔
141
            // add to sum
142
            res += res2;
89,419✔
143
        }
144
        // l=k
145
        Real res2 = 1.0;
146
        // alpha_k zeta_k
147
        res2 *=
148
            revZero(k)
8,359✔
149
                ? Real(vol(k) * vol(k) / 4.0 *
8,359✔
UNCOV
150
                      (4.0 * pow(cappedTime(k + 1, t) - time2(k), 2.0) -
×
UNCOV
151
                       (pow(flooredTime(k, w) - 2.0 * time2(k) +
×
UNCOV
152
                                cappedTime(k + 1, t),
×
UNCOV
153
                            2.0) +
×
UNCOV
154
                        pow(cappedTime(k + 1, t) - flooredTime(k, w), 2.0))))
×
155
                : Real(vol(k) * vol(k) / (2.0 * rev(k) * rev(k)) *
8,359✔
156
                      (exp(-2.0 * rev(k) * (cappedTime(k + 1, t) - time2(k))) +
8,359✔
157
                       1.0 -
8,359✔
158
                       (exp(-rev(k) * (flooredTime(k, w) - 2.0 * time2(k) +
8,359✔
159
                                       cappedTime(k + 1, t))) +
8,359✔
160
                        exp(-rev(k) *
8,359✔
161
                            (cappedTime(k + 1, t) - flooredTime(k, w))))));
8,359✔
162
        // zeta_i (i>k)
163
        for (int i = k + 1; i <= upperIndex(t) - 1; i++)
68,195✔
164
            res2 *= exp(-rev(i) * (cappedTime(i + 1, t) - time2(i)));
59,836✔
165
        // no beta_j in this case ...
166
        res += res2;
8,359✔
167
    }
168

169
    cache2a_.insert(std::make_pair(key, res));
1,767✔
170

171
    return res;
1,767✔
172
} // expectation_rn_part
173

174
Real GsrProcessCore::expectation_tf_part(const Time w,
4,306,693✔
175
                                         const Time dt) const {
176

177
    Real t = w + dt;
4,306,693✔
178

179
    std::pair<Real, Real> key;
180
    key = std::make_pair(w, t);
181
    auto k =
182
        cache2b_.find(key);
183
    if (k != cache2b_.end())
4,306,693✔
184
        return k->second;
4,304,926✔
185

186
    Real res = 0.0;
187
    // int -A(s,t) \sigma^2 G(s,T)
188
    for (int k = lowerIndex(w); k <= upperIndex(t) - 1; k++) {
10,126✔
189
        Real res2 = 0.0;
190
        // l>k
191
        for (int l = k + 1; l <= upperIndex(T_) - 1; l++) {
142,815✔
192
            Real res3 = 1.0;
193
            // eta_l
194
            res3 *= revZero(l)
134,456✔
195
                        ? Real(cappedTime(l + 1, T_) - time2(l))
134,456✔
196
                        : (1.0 -
134,456✔
197
                           exp(-rev(l) * (cappedTime(l + 1, T_) - time2(l)))) /
134,456✔
198
                              rev(l);
134,456✔
199
            // zeta_i (i>k)
200
            for (int i = k + 1; i <= upperIndex(t) - 1; i++)
1,955,285✔
201
                res3 *= exp(-rev(i) * (cappedTime(i + 1, t) - time2(i)));
1,820,829✔
202
            // gamma_j (j>k)
203
            for (int j = k + 1; j <= l - 1; j++)
2,144,512✔
204
                res3 *= exp(-rev(j) * (time2(j + 1) - time2(j)));
2,010,056✔
205
            // zeta_k gamma_k
206
            res3 *=
134,456✔
207
                revZero(k)
134,456✔
208
                    ? Real((cappedTime(k + 1, t) - time2(k + 1) -
134,456✔
209
                       (2.0 * flooredTime(k, w) - cappedTime(k + 1, t) -
×
210
                        time2(k + 1))) /
211
                          2.0)
212
                    : Real((exp(rev(k) * (cappedTime(k + 1, t) - time2(k + 1))) -
134,456✔
213
                       exp(rev(k) * (2.0 * flooredTime(k, w) -
134,456✔
214
                                     cappedTime(k + 1, t) - time2(k + 1)))) /
134,456✔
215
                          (2.0 * rev(k)));
134,456✔
216
            // add to sum
217
            res2 += res3;
134,456✔
218
        }
219
        // l=k
220
        Real res3 = 1.0;
221
        // eta_k zeta_k
222
        res3 *=
223
            revZero(k)
8,359✔
224
                ? Real((-pow(cappedTime(k + 1, t) - cappedTime(k + 1, T_), 2.0) -
8,359✔
UNCOV
225
                   2.0 * pow(cappedTime(k + 1, t) - flooredTime(k, w), 2.0) +
×
UNCOV
226
                   pow(2.0 * flooredTime(k, w) - cappedTime(k + 1, T_) -
×
UNCOV
227
                           cappedTime(k + 1, t),
×
228
                       2.0)) /
229
                      4.0)
230
                : Real((2.0 - exp(rev(k) *
8,359✔
231
                             (cappedTime(k + 1, t) - cappedTime(k + 1, T_))) -
8,359✔
232
                   (2.0 * exp(-rev(k) *
8,359✔
233
                              (cappedTime(k + 1, t) - flooredTime(k, w))) -
8,359✔
234
                    exp(rev(k) *
8,359✔
235
                        (2.0 * flooredTime(k, w) - cappedTime(k + 1, T_) -
8,359✔
236
                         cappedTime(k + 1, t))))) /
8,359✔
237
                      (2.0 * rev(k) * rev(k)));
8,359✔
238
        // zeta_i (i>k)
239
        for (int i = k + 1; i <= upperIndex(t) - 1; i++)
68,195✔
240
            res3 *= exp(-rev(i) * (cappedTime(i + 1, t) - time2(i)));
59,836✔
241
        // no gamma_j in this case ...
242
        res2 += res3;
8,359✔
243
        // add to main accumulator
244
        res += -vol(k) * vol(k) * res2;
8,359✔
245
    }
246

247
    cache2b_.insert(std::make_pair(key, res));
1,767✔
248

249
    return res;
1,767✔
250
} // expectation_tf_part
251

252
Real GsrProcessCore::variance(const Time w, const Time dt) const {
4,306,693✔
253

254
    Real t = w + dt;
4,306,693✔
255

256
    std::pair<Real, Real> key;
257
    key = std::make_pair(w, t);
258
    auto k = cache3_.find(key);
259
    if (k != cache3_.end())
4,306,693✔
260
        return k->second;
4,304,926✔
261

262
    Real res = 0.0;
263
    for (int k = lowerIndex(w); k <= upperIndex(t) - 1; k++) {
10,126✔
264
        Real res2 = vol(k) * vol(k);
8,359✔
265
        // zeta_k^2
266
        res2 *= revZero(k)
8,359✔
267
                    ? Real(-(flooredTime(k, w) - cappedTime(k + 1, t)))
8,359✔
268
                    : (1.0 - exp(2.0 * rev(k) *
8,359✔
269
                                 (flooredTime(k, w) - cappedTime(k + 1, t)))) /
8,359✔
270
                          (2.0 * rev(k));
8,359✔
271
        // zeta_i (i>k)
272
        for (int i = k + 1; i <= upperIndex(t) - 1; i++) {
68,195✔
273
            res2 *= exp(-2.0 * rev(i) * (cappedTime(i + 1, t) - time2(i)));
59,836✔
274
        }
275
        res += res2;
8,359✔
276
    }
277

278
    cache3_.insert(std::make_pair(key, res));
1,767✔
279
    return res;
1,767✔
280
}
281

282
Real GsrProcessCore::y(const Time t) const {
4,271,377✔
283
    Real key;
284
    key = t;
4,271,377✔
285
    auto k = cache4_.find(key);
286
    if (k != cache4_.end())
4,271,377✔
287
        return k->second;
4,270,744✔
288

289
    Real res = 0.0;
290
    for (int i = 0; i <= upperIndex(t) - 1; i++) {
4,407✔
291
        Real res2 = 1.0;
292
        for (int j = i + 1; j <= upperIndex(t) - 1; j++) {
23,735✔
293
            res2 *= exp(-2.0 * rev(j) * (cappedTime(j + 1, t) - time2(j)));
19,961✔
294
        }
295
        res2 *= revZero(i) ? Real(vol(i) * vol(i) * (cappedTime(i + 1, t) - time2(i)))
3,774✔
296
                           : (vol(i) * vol(i) / (2.0 * rev(i)) *
3,774✔
297
                              (1.0 - exp(-2.0 * rev(i) *
3,774✔
298
                                         (cappedTime(i + 1, t) - time2(i)))));
3,774✔
299
        res += res2;
3,774✔
300
    }
301

302
    cache4_.insert(std::make_pair(key, res));
633✔
303
    return res;
633✔
304
}
305

306
Real GsrProcessCore::G(const Time t, const Time w) const {
4,271,377✔
307
    std::pair<Real, Real> key;
308
    key = std::make_pair(w, t);
309
    auto k = cache5_.find(key);
310
    if (k != cache5_.end())
4,271,377✔
311
        return k->second;
4,261,332✔
312

313
    Real res = 0.0;
314
    for (int i = lowerIndex(t); i <= upperIndex(w) - 1; i++) {
48,628✔
315
        Real res2 = 1.0;
316
        for (int j = lowerIndex(t); j <= i - 1; j++) {
157,049✔
317
            res2 *= exp(-rev(j) * (time2(j + 1) - flooredTime(j, t)));
118,466✔
318
        }
319
        res2 *= revZero(i) ? Real(cappedTime(i + 1, w) - flooredTime(i, t))
38,583✔
320
                           : (1.0 - exp(-rev(i) * (cappedTime(i + 1, w) -
38,583✔
321
                                                   flooredTime(i, t)))) /
38,583✔
322
                                 rev(i);
38,583✔
323
        res += res2;
38,583✔
324
    }
325

326
    cache5_.insert(std::make_pair(key, res));
10,045✔
327
    return res;
10,045✔
328
}
329

330
int GsrProcessCore::lowerIndex(const Time t) const {
55,696✔
331
    return static_cast<int>(std::upper_bound(times_.begin(), times_.end(), t) -
55,696✔
332
                            times_.begin());
55,696✔
333
}
334

335
int GsrProcessCore::upperIndex(const Time t) const {
3,365,382✔
336
    if (t < QL_MIN_POSITIVE_REAL)
3,365,382✔
337
        return 0;
338
    return static_cast<int>(
339
               std::upper_bound(times_.begin(), times_.end(), t - QL_EPSILON) -
3,365,382✔
340
               times_.begin()) +
3,365,382✔
341
           1;
3,365,382✔
342
}
343

344
Real GsrProcessCore::cappedTime(const Size index, const Real cap) const {
3,584,455✔
345
    return cap != Null<Real>() ? std::min(cap, time2(index)) : time2(index);
6,838,165✔
346
}
347

348
Real GsrProcessCore::flooredTime(const Size index,
431,078✔
349
                                 const Real floor) const {
350
    return floor != Null<Real>() ? std::max(floor, time2(index)) : time2(index);
791,544✔
351
}
352

353
Real GsrProcessCore::time2(const Size index) const {
14,046,627✔
354
    if (index == 0)
14,046,627✔
355
        return 0.0;
356
    if (index > times_.size())
14,013,771✔
357
        return T_; // FIXME how to ensure that forward
46,111✔
358
                   // measure time is geq all times
359
                   // given
360
    return times_[index - 1];
13,967,660✔
361
}
362

363
Real GsrProcessCore::vol(const Size index) const {
118,270✔
364
    if (index >= vols_.size())
118,270✔
UNCOV
365
        return vols_.back();
×
366
    return vols_[index];
118,270✔
367
}
368

369
Real GsrProcessCore::rev(const Size index) const {
7,316,649✔
370
    if (index >= reversions_.size())
7,316,649✔
371
        return reversions_.back();
489,157✔
372
    return reversions_[index];
6,827,492✔
373
}
374

375
bool GsrProcessCore::revZero(const Size index) const {
515,184✔
376
    if (index >= revZero_.size())
515,184✔
377
        return revZero_.back();
98,413✔
378
    return revZero_[index];
379
}
380

381
} // namesapce QuantLib
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