Skip to content

Commit 1e0a002

Browse files
pcaspersjenkins
authored andcommitted
QPR-11618 factor out backward solver interface
1 parent a0eacbb commit 1e0a002

9 files changed

Lines changed: 100 additions & 55 deletions

QuantExt/qle/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ models/irmodel.hpp
713713
models/jyimpliedyoyinflationtermstructure.hpp
714714
models/jyimpliedzeroinflationtermstructure.hpp
715715
models/lgm.hpp
716+
models/lgmbackwardsolver.hpp
716717
models/lgmcalibrationinfo.hpp
717718
models/lgmconvolutionsolver2.hpp
718719
models/lgmfdsolver.hpp
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
Copyright (C) 2024 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+
19+
/*! \file lgmbackwardsolver.hpp
20+
\brief interface for LGM1F backward solver
21+
22+
\ingroup engines
23+
*/
24+
25+
#pragma once
26+
27+
#include <qle/math/randomvariable.hpp>
28+
#include <qle/models/lgm.hpp>
29+
30+
namespace QuantExt {
31+
32+
//! Interface for LGM1F backward solver
33+
class LgmBackwardSolver {
34+
public:
35+
virtual ~LgmBackwardSolver() {}
36+
37+
/* get grid size */
38+
virtual Size gridSize() const = 0;
39+
40+
/* get discretised states grid at time t */
41+
virtual RandomVariable stateGrid(const Real t) const = 0;
42+
43+
/* roll back an deflated NPV array from t1 to t0 using the given number of steps or,
44+
if the number of steps is not given, an appropriate number of steps which will
45+
generally depend on the numerical method that is used. */
46+
virtual RandomVariable rollback(const RandomVariable& v, const Real t1, const Real t0,
47+
Size steps = Null<Size>()) const = 0;
48+
49+
/* the underlying model */
50+
virtual const boost::shared_ptr<LinearGaussMarkovModel>& model() const = 0;
51+
};
52+
53+
} // namespace QuantExt

QuantExt/qle/models/lgmconvolutionsolver2.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ RandomVariable LgmConvolutionSolver2::stateGrid(const Real t) const {
6868
return x;
6969
}
7070

71-
RandomVariable LgmConvolutionSolver2::rollback(const RandomVariable& v, const Real t1, const Real t0) const {
71+
RandomVariable LgmConvolutionSolver2::rollback(const RandomVariable& v, const Real t1, const Real t0, Size) const {
7272
if (QuantLib::close_enough(t0, t1) || v.deterministic())
7373
return v;
7474
QL_REQUIRE(t0 < t1, "LgmConvolutionSolver2::rollback(): t0 (" << t0 << ") < t1 (" << t1 << ") required.");

QuantExt/qle/models/lgmconvolutionsolver2.hpp

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
#pragma once
2626

2727
#include <qle/math/randomvariable.hpp>
28-
#include <qle/models/lgm.hpp>
28+
#include <qle/models/lgmbackwardsolver.hpp>
2929

3030
namespace QuantExt {
3131

@@ -34,22 +34,16 @@ namespace QuantExt {
3434
exercise into swaptions
3535
*/
3636

37-
class LgmConvolutionSolver2 {
37+
class LgmConvolutionSolver2 : public LgmBackwardSolver {
3838
public:
3939
LgmConvolutionSolver2(const boost::shared_ptr<LinearGaussMarkovModel>& model, const Real sy, const Size ny,
4040
const Real sx, const Size nx);
41-
42-
/* get grid size */
43-
Size gridSize() const { return 2 * mx_ + 1; }
44-
45-
/* get discretised states grid at time t */
46-
RandomVariable stateGrid(const Real t) const;
47-
48-
/* roll back an deflated NPV array from t1 to t0 */
49-
RandomVariable rollback(const RandomVariable& v, const Real t1, const Real t0) const;
50-
51-
/* the underlying model */
52-
const boost::shared_ptr<LinearGaussMarkovModel>& model() const { return model_; }
41+
Size gridSize() const override { return 2 * mx_ + 1; }
42+
RandomVariable stateGrid(const Real t) const override;
43+
// steps are always ignored, since we can take large steps
44+
RandomVariable rollback(const RandomVariable& v, const Real t1, const Real t0,
45+
Size steps = Null<Size>()) const override;
46+
const boost::shared_ptr<LinearGaussMarkovModel>& model() const override { return model_; }
5347

5448
private:
5549
boost::shared_ptr<LinearGaussMarkovModel> model_;

QuantExt/qle/models/lgmfdsolver.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ LgmFdSolver::LgmFdSolver(const boost::shared_ptr<LinearGaussMarkovModel>& model,
4141

4242
Size LgmFdSolver::gridSize() const { return stateGridPoints_; }
4343

44-
RandomVariable LgmFdSolver::stateGrid() const { return mesherLocations_; }
44+
RandomVariable LgmFdSolver::stateGrid(Real) const { return mesherLocations_; }
4545

4646
const boost::shared_ptr<LinearGaussMarkovModel>& LgmFdSolver::model() const { return model_; }
4747

QuantExt/qle/models/lgmfdsolver.hpp

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,24 @@
2525
#pragma once
2626

2727
#include <qle/math/randomvariable.hpp>
28-
#include <qle/models/lgm.hpp>
28+
#include <qle/models/lgmbackwardsolver.hpp>
2929

3030
#include <ql/methods/finitedifferences/meshers/fdmmesher.hpp>
3131
#include <ql/methods/finitedifferences/solvers/fdmbackwardsolver.hpp>
3232

3333
namespace QuantExt {
3434

3535
//! Numerical FD solver for the LGM model
36-
class LgmFdSolver {
36+
class LgmFdSolver : LgmBackwardSolver {
3737
public:
3838
LgmFdSolver(const boost::shared_ptr<LinearGaussMarkovModel>& model, const Real maxTime = 50.0,
3939
const Size stateGridPoints = 64, const Size timeStepsPerYear = 24, const Real mesherEpsilon = 1E-4);
40-
41-
/* get grid size */
42-
Size gridSize() const;
43-
44-
/* get discretised states grid */
45-
RandomVariable stateGrid() const;
46-
47-
/* roll back an deflated NPV array from t1 to t0 using the given number of steps or,
48-
if that is not given, the time steps per year specified in the constructor */
49-
RandomVariable rollback(const RandomVariable& v, const Real t1, const Real t0, Size steps = Null<Size>()) const;
50-
51-
/* the underlying model */
52-
const boost::shared_ptr<LinearGaussMarkovModel>& model() const;
40+
Size gridSize() const override;
41+
RandomVariable stateGrid(const Real t) const override;
42+
// if steps are not given, the time steps per year specified in the constructor
43+
RandomVariable rollback(const RandomVariable& v, const Real t1, const Real t0,
44+
Size steps = Null<Size>()) const override;
45+
const boost::shared_ptr<LinearGaussMarkovModel>& model() const override;
5346

5447
private:
5548
boost::shared_ptr<LinearGaussMarkovModel> model_;

QuantExt/qle/pricingengines/numericlgmmultilegoptionengine.cpp

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,8 @@ RandomVariable getRebatePv(const LgmVectorised& lgm, const Real t, const RandomV
185185
}
186186

187187
NumericLgmMultiLegOptionEngineBase::NumericLgmMultiLegOptionEngineBase(
188-
const boost::shared_ptr<LinearGaussMarkovModel>& model, const Real sy, const Size ny, const Real sx, const Size nx,
189-
const Handle<YieldTermStructure>& discountCurve)
190-
: LgmConvolutionSolver2(model, sy, ny, sx, nx), discountCurve_(discountCurve) {}
188+
const boost::shared_ptr<LgmBackwardSolver>& solver, const Handle<YieldTermStructure>& discountCurve)
189+
: solver_(solver), discountCurve_(discountCurve) {}
191190

192191
void NumericLgmMultiLegOptionEngineBase::calculate() const {
193192

@@ -198,7 +197,7 @@ void NumericLgmMultiLegOptionEngineBase::calculate() const {
198197
TODO we should add exercise data to indicate past exercises and value outstanding cash settlement amounts,
199198
or - possibly (?) - the physical underlying in that we exercised into? Or would that be a separate trade? */
200199

201-
Date refDate = model()->parametrization()->termStructure()->referenceDate();
200+
Date refDate = solver_->model()->parametrization()->termStructure()->referenceDate();
202201

203202
/* we might have to handle an exercise with rebate, init a variable for that (null if not applicable) */
204203

@@ -279,23 +278,23 @@ void NumericLgmMultiLegOptionEngineBase::calculate() const {
279278

280279
/* Step backwards through simulation dates and compute the option npv */
281280

282-
LgmVectorised lgm(model()->parametrization());
281+
LgmVectorised lgm(solver_->model()->parametrization());
283282

284283
// the current option npv that we roll back through the grid
285-
RandomVariable optionNpv(gridSize(), 0.0);
284+
RandomVariable optionNpv(solver_->gridSize(), 0.0);
286285
// cashflow npvs that are part of the latest exercise into option
287-
RandomVariable underlyingNpv1(gridSize(), 0.0);
286+
RandomVariable underlyingNpv1(solver_->gridSize(), 0.0);
288287
// cashflow npvs that are estimated, but not yet part of the latest exercise into option
289288
std::map<std::pair<Size, Size>, RandomVariable> underlyingNpv2;
290289

291290
for (auto it = simulationDates.rbegin(); it != simulationDates.rend(); ++it) {
292291

293-
Real t_from = model()->parametrization()->termStructure()->timeFromReference(*it);
292+
Real t_from = solver_->model()->parametrization()->termStructure()->timeFromReference(*it);
294293
Real t_to = (it != std::next(simulationDates.rend(), -1))
295-
? model()->parametrization()->termStructure()->timeFromReference(*std::next(it, 1))
294+
? solver_->model()->parametrization()->termStructure()->timeFromReference(*std::next(it, 1))
296295
: t_from;
297296

298-
RandomVariable state = stateGrid(t_from);
297+
RandomVariable state = solver_->stateGrid(t_from);
299298

300299
auto option = optionDates.find(*it);
301300
auto cashflow = cashflowDates.find(*it);
@@ -305,7 +304,7 @@ void NumericLgmMultiLegOptionEngineBase::calculate() const {
305304
if (cashflow != cashflowDates.end()) {
306305
for (auto const& cf : cashflow->second) {
307306
auto tmp = getUnderlyingCashflowPv(lgm, t_from, state, discountCurve_, legs_[cf.first][cf.second]) *
308-
RandomVariable(gridSize(), payer_[cf.first]);
307+
RandomVariable(solver_->gridSize(), payer_[cf.first]);
309308
auto it = underlyingNpv2.find(cf);
310309
if (it != underlyingNpv2.end())
311310
it->second += tmp;
@@ -334,11 +333,11 @@ void NumericLgmMultiLegOptionEngineBase::calculate() const {
334333
// rollback
335334

336335
if (t_from != t_to) {
337-
underlyingNpv1 = rollback(underlyingNpv1, t_from, t_to);
336+
underlyingNpv1 = solver_->rollback(underlyingNpv1, t_from, t_to);
338337
for (auto& c : underlyingNpv2) {
339-
c.second = rollback(c.second, t_from, t_to);
338+
c.second = solver_->rollback(c.second, t_from, t_to);
340339
}
341-
optionNpv = rollback(optionNpv, t_from, t_to);
340+
optionNpv = solver_->rollback(optionNpv, t_from, t_to);
342341
}
343342
}
344343

@@ -350,7 +349,7 @@ void NumericLgmMultiLegOptionEngineBase::calculate() const {
350349
underlyingNpv_ += c.second.at(0);
351350
}
352351

353-
additionalResults_ = getAdditionalResultsMap(model()->getCalibrationInfo());
352+
additionalResults_ = getAdditionalResultsMap(solver_->model()->getCalibrationInfo());
354353

355354
if (rebatedExercise) {
356355
for (Size i = 0; i < rebatedExercise->dates().size(); ++i) {
@@ -366,8 +365,9 @@ NumericLgmMultiLegOptionEngine::NumericLgmMultiLegOptionEngine(const boost::shar
366365
const Real sy, const Size ny, const Real sx,
367366
const Size nx,
368367
const Handle<YieldTermStructure>& discountCurve)
369-
: NumericLgmMultiLegOptionEngineBase(model, sy, ny, sx, nx, discountCurve) {
370-
registerWith(LgmConvolutionSolver2::model());
368+
: NumericLgmMultiLegOptionEngineBase(boost::make_shared<LgmConvolutionSolver2>(model, sy, ny, sx, nx),
369+
discountCurve) {
370+
registerWith(solver_->model());
371371
registerWith(discountCurve_);
372372
}
373373

@@ -390,8 +390,9 @@ void NumericLgmMultiLegOptionEngine::calculate() const {
390390
NumericLgmSwaptionEngine::NumericLgmSwaptionEngine(const boost::shared_ptr<LinearGaussMarkovModel>& model,
391391
const Real sy, const Size ny, const Real sx, const Size nx,
392392
const Handle<YieldTermStructure>& discountCurve)
393-
: NumericLgmMultiLegOptionEngineBase(model, sy, ny, sx, nx, discountCurve) {
394-
registerWith(LgmConvolutionSolver2::model());
393+
: NumericLgmMultiLegOptionEngineBase(boost::make_shared<LgmConvolutionSolver2>(model, sy, ny, sx, nx),
394+
discountCurve) {
395+
registerWith(solver_->model());
395396
registerWith(discountCurve_);
396397
}
397398

@@ -413,8 +414,9 @@ void NumericLgmSwaptionEngine::calculate() const {
413414
NumericLgmNonstandardSwaptionEngine::NumericLgmNonstandardSwaptionEngine(
414415
const boost::shared_ptr<LinearGaussMarkovModel>& model, const Real sy, const Size ny, const Real sx, const Size nx,
415416
const Handle<YieldTermStructure>& discountCurve)
416-
: NumericLgmMultiLegOptionEngineBase(model, sy, ny, sx, nx, discountCurve) {
417-
registerWith(LgmConvolutionSolver2::model());
417+
: NumericLgmMultiLegOptionEngineBase(boost::make_shared<LgmConvolutionSolver2>(model, sy, ny, sx, nx),
418+
discountCurve) {
419+
registerWith(solver_->model());
418420
registerWith(discountCurve_);
419421
}
420422

QuantExt/qle/pricingengines/numericlgmmultilegoptionengine.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,16 @@
2828

2929
namespace QuantExt {
3030

31-
class NumericLgmMultiLegOptionEngineBase : protected LgmConvolutionSolver2 {
31+
class NumericLgmMultiLegOptionEngineBase {
3232
public:
33-
NumericLgmMultiLegOptionEngineBase(const boost::shared_ptr<LinearGaussMarkovModel>& model, const Real sy,
34-
const Size ny, const Real sx, const Size nx,
33+
NumericLgmMultiLegOptionEngineBase(const boost::shared_ptr<LgmBackwardSolver>& solver,
3534
const Handle<YieldTermStructure>& discountCurve = Handle<YieldTermStructure>());
3635

3736
protected:
3837
void calculate() const;
3938

39+
// inputs set in ctor
40+
boost::shared_ptr<LgmBackwardSolver> solver_;
4041
Handle<YieldTermStructure> discountCurve_;
4142

4243
// inputs set by derived classes

QuantExt/qle/quantext.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@
321321
#include <qle/models/jyimpliedyoyinflationtermstructure.hpp>
322322
#include <qle/models/jyimpliedzeroinflationtermstructure.hpp>
323323
#include <qle/models/lgm.hpp>
324+
#include <qle/models/lgmbackwardsolver.hpp>
324325
#include <qle/models/lgmcalibrationinfo.hpp>
325326
#include <qle/models/lgmconvolutionsolver2.hpp>
326327
#include <qle/models/lgmfdsolver.hpp>

0 commit comments

Comments
 (0)