From ee20b5b90f25ee27dbaecea5163e0b37bd8a2798 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:21:55 +0200 Subject: [PATCH 01/15] 1D Simple mesher add --- .../Meshers/FdmSimpleProcess1dMesher.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs diff --git a/src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs b/src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs new file mode 100644 index 000000000..b52eb4d1c --- /dev/null +++ b/src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs @@ -0,0 +1,74 @@ +/* + Copyright (C) 2017 Jean-Camille Tournier (jean-camille.tournier@avivainvestors.com) + + This file is part of QLNet Project https://github.com/amaggiulli/qlnet + + QLNet is free software: you can redistribute it and/or modify it + under the terms of the QLNet license. You should have received a + copy of the license along with this program; if not, license is + available online at . + + QLNet is a based on QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + The QuantLib license is available online at http://quantlib.org/license.shtml. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace QLNet +{ + /// + /// One-dimensional grid mesher + /// + public class FdmSimpleProcess1dMesher : Fdm1dMesher + { + public FdmSimpleProcess1dMesher(int size, + StochasticProcess1D process, + double maturity, int tAvgSteps = 10, + double epsilon = 0.0001, + double? mandatoryPoint = null) + : base(size) + { + locations_ = new InitializedList(locations_.Count, 0.0); + for (int l=1; l<=tAvgSteps; ++l) + { + double t = (maturity*l)/tAvgSteps; + + double mp = (mandatoryPoint != null) ? mandatoryPoint.Value + : process.x0(); + + double qMin = Math.Min(Math.Min(mp, process.x0()), + process.evolve(0, process.x0(), t, + new InverseCumulativeNormal().value(epsilon))); + double qMax = Math.Max(Math.Max(mp, process.x0()), + process.evolve(0, process.x0(), t, + new InverseCumulativeNormal().value(1 - epsilon))); + + double dp = (1 - 2 * epsilon) / (size - 1); + double p = epsilon; + locations_[0] += qMin; + + for (int i=1; i < size-1; ++i) + { + p += dp; + locations_[i] += process.evolve(0, process.x0(), t, + new InverseCumulativeNormal().value(p)); + } + locations_[locations_.Count() - 1] += qMax; + } + locations_ = locations_.Select(x => x / tAvgSteps).ToList(); + for (int i=0; i < size-1; ++i) + { + dminus_[i+1] = dplus_[i] = locations_[i+1] - locations_[i]; + } + + dplus_[dplus_.Count - 1] = null; + dminus_[0] = null; + } + } +} From a0161badfeb67301683f48dfed1e8d84b45bc079 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:22:43 +0200 Subject: [PATCH 02/15] Hull White operator add --- .../Operators/FdmHullWhiteOp.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs diff --git a/src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs b/src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs new file mode 100644 index 000000000..46914f197 --- /dev/null +++ b/src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs @@ -0,0 +1,105 @@ +/* + Copyright (C) 2017 Jean-Camille Tournier (jean-camille.tournier@avivainvestors.com) + + This file is part of QLNet Project https://github.com/amaggiulli/qlnet + + QLNet is free software: you can redistribute it and/or modify it + under the terms of the QLNet license. You should have received a + copy of the license along with this program; if not, license is + available online at . + + QLNet is a based on QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + The QuantLib license is available online at http://quantlib.org/license.shtml. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ +using System; +using System.Collections.Generic; + +namespace QLNet +{ + public class FdmHullWhiteOp : FdmLinearOpComposite + { + public FdmHullWhiteOp(FdmMesher mesher, + HullWhite model, + int direction) + { + x_ = mesher.locations(direction); + dzMap_ = new TripleBandLinearOp(new FirstDerivativeOp(direction, mesher).mult(-1.0 * x_ * model.a()).add( + new SecondDerivativeOp(direction, mesher).mult(0.5 * model.sigma() * model.sigma() + * new Vector(mesher.layout().size(), 1.0)))); + mapT_ = new TripleBandLinearOp(direction, mesher); + direction_ = direction; + model_ = model; + } + public override int size() { return 1; } + + //! Time \f$t1 <= t2\f$ is required + public override void setTime(double t1, double t2) + { + OneFactorModel.ShortRateDynamics dynamics = model_.dynamics(); + + double phi = 0.5* ( dynamics.shortRate(t1, 0.0) + + dynamics.shortRate(t2, 0.0)); + + mapT_.axpyb(new Vector(), dzMap_, dzMap_, -1.0 * (x_ + phi)); + } + + public override Vector apply(Vector r) + { + return mapT_.apply(r); + } + + public override Vector apply_mixed(Vector r) { + Vector retVal = new Vector(r.size(), 0.0); + return retVal; + } + + public override Vector apply_direction(int direction, Vector r) { + if (direction == direction_) + return mapT_.apply(r); + else { + Vector retVal = new Vector(r.size(), 0.0); + return retVal; + } + } + public override Vector solve_splitting(int direction, Vector r, double dt) { + if (direction == direction_) + return mapT_.solve_splitting(r, dt, 1.0); + else { + Vector retVal = new Vector(r.size(), 0.0); + return retVal; + } + } + public override Vector preconditioner(Vector r, double dt) { return solve_splitting(direction_, r, dt); } + + public override List toMatrixDecomp() + { + List retVal = new InitializedList(1, mapT_.toMatrix()); + return retVal; + } + + #region IOperator interface + public override IOperator identity(int size) { return null; } + public override Vector applyTo(Vector v) { return null; } + public override Vector solveFor(Vector rhs) { return null; } + + public override IOperator multiply(double a, IOperator D) { return null; } + public override IOperator add(IOperator A, IOperator B) { return null; } + public override IOperator subtract(IOperator A, IOperator B) { return null; } + + public override bool isTimeDependent() { return false; } + public override void setTime(double t) { } + public override object Clone() { return this.MemberwiseClone(); } + #endregion + + protected HullWhite model_; + protected Vector x_; + protected TripleBandLinearOp dzMap_; + protected TripleBandLinearOp mapT_; + protected int direction_; + } +} From 103091cdb1c34ffc0202793d67ab404f5bb208de Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:23:17 +0200 Subject: [PATCH 03/15] TripleBandLinear op bug fix --- .../Operators/TripleBandLinearOp.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/QLNet/Methods/Finitedifferences/Operators/TripleBandLinearOp.cs b/src/QLNet/Methods/Finitedifferences/Operators/TripleBandLinearOp.cs index 707b286ea..7f55a9196 100644 --- a/src/QLNet/Methods/Finitedifferences/Operators/TripleBandLinearOp.cs +++ b/src/QLNet/Methods/Finitedifferences/Operators/TripleBandLinearOp.cs @@ -65,12 +65,12 @@ public TripleBandLinearOp(int direction, FdmMesher mesher) public TripleBandLinearOp(TripleBandLinearOp m) { direction_ = m.direction_; - i0_ = new List(m.mesher_.layout().size()); - i2_ = new List(m.mesher_.layout().size()); - reverseIndex_ = new List(m.mesher_.layout().size()); - lower_ = new List(m.mesher_.layout().size()); - diag_ = new List(m.mesher_.layout().size()); - upper_ = new List(m.mesher_.layout().size()); + i0_ = new InitializedList(m.mesher_.layout().size()); + i2_ = new InitializedList(m.mesher_.layout().size()); + reverseIndex_ = new InitializedList(m.mesher_.layout().size()); + lower_ = new InitializedList(m.mesher_.layout().size()); + diag_ = new InitializedList(m.mesher_.layout().size()); + upper_ = new InitializedList(m.mesher_.layout().size()); mesher_ = m.mesher_; int len = m.mesher_.layout().size(); From 2ffa5dfd90f966610b8a7b1eb821cdf595e46d55 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:24:36 +0200 Subject: [PATCH 04/15] FDM Hull White Solver add --- .../Solvers/FdmHullWhiteSolver.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/QLNet/Methods/Finitedifferences/Solvers/FdmHullWhiteSolver.cs diff --git a/src/QLNet/Methods/Finitedifferences/Solvers/FdmHullWhiteSolver.cs b/src/QLNet/Methods/Finitedifferences/Solvers/FdmHullWhiteSolver.cs new file mode 100644 index 000000000..f0b11c3ee --- /dev/null +++ b/src/QLNet/Methods/Finitedifferences/Solvers/FdmHullWhiteSolver.cs @@ -0,0 +1,65 @@ +/* + Copyright (C) 2017 Jean-Camille Tournier (jean-camille.tournier@avivainvestors.com) + + This file is part of QLNet Project https://github.com/amaggiulli/qlnet + + QLNet is free software: you can redistribute it and/or modify it + under the terms of the QLNet license. You should have received a + copy of the license along with this program; if not, license is + available online at . + + QLNet is a based on QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + The QuantLib license is available online at http://quantlib.org/license.shtml. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +using System; + +namespace QLNet +{ + public class FdmHullWhiteSolver : LazyObject + { + public FdmHullWhiteSolver( + Handle model, + FdmSolverDesc solverDesc, + FdmSchemeDesc schemeDesc = null) + { + solverDesc_ = solverDesc; + schemeDesc_ = schemeDesc ?? new FdmSchemeDesc().Hundsdorfer(); + model_ = model; + model_.registerWith(update); + } + + public double valueAt(double s) + { + calculate(); + return solver_.interpolateAt(s); + } + public double deltaAt(double s) { + return 0.0; + } + public double gammaAt(double s) { + return 0.0; + } + public double thetaAt(double s) { + return 0.0; + } + + protected override void performCalculations() + { + FdmHullWhiteOp op = new FdmHullWhiteOp( + solverDesc_.mesher, model_.currentLink(), 0); + + solver_ = new Fdm1DimSolver(solverDesc_, schemeDesc_, op); + } + + protected Handle model_; + protected FdmSolverDesc solverDesc_; + protected FdmSchemeDesc schemeDesc_; + protected Fdm1DimSolver solver_; + } +} From ac9f04a20005b7024dfc9354adb5d2e81e7964a9 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:25:14 +0200 Subject: [PATCH 05/15] Bermudan condition bug fix --- .../StepConditions/FdmBermudanStepCondition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs b/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs index 5b53a6adc..d4a315f88 100644 --- a/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs +++ b/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs @@ -46,7 +46,7 @@ public FdmBermudanStepCondition(List exerciseDates, public void applyTo(object o, double t) { Vector a = (Vector) o; - if (exerciseTimes_.BinarySearch(t) != exerciseTimes_.Count) + if (exerciseTimes_.BinarySearch(t) > 0) { FdmLinearOpLayout layout = mesher_.layout(); FdmLinearOpIterator endIter = layout.end(); From 48780ce12da77cf4595b2eb777f441b5961b4558 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:26:08 +0200 Subject: [PATCH 06/15] Affine model utilities add --- .../Utilities/FdmAffineModelSwapInnerValue.cs | 132 ++++++++++++++++++ .../Utilities/FdmAffineModelTermStructure.cs | 59 ++++++++ 2 files changed, 191 insertions(+) create mode 100644 src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs create mode 100644 src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs diff --git a/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs new file mode 100644 index 000000000..4309b459f --- /dev/null +++ b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs @@ -0,0 +1,132 @@ +/* + Copyright (C) 2017 Jean-Camille Tournier (jean-camille.tournier@avivainvestors.com) + + This file is part of QLNet Project https://github.com/amaggiulli/qlnet + + QLNet is free software: you can redistribute it and/or modify it + under the terms of the QLNet license. You should have received a + copy of the license along with this program; if not, license is + available online at . + + QLNet is a based on QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + The QuantLib license is available online at http://quantlib.org/license.shtml. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace QLNet +{ + public class FdmAffineModelSwapInnerValue : FdmInnerValueCalculator where ModelType : ITermStructureConsistentModel, IAffineModel + { + public FdmAffineModelSwapInnerValue( + ModelType disModel, + ModelType fwdModel, + VanillaSwap swap, + Dictionary exerciseDates, + FdmMesher mesher, + int direction) + { + disModel_ = disModel; + fwdModel_ = fwdModel; + mesher_ = mesher; + direction_ = direction; + swap_ = new VanillaSwap(swap.swapType, + swap.nominal, + swap.fixedSchedule(), + swap.fixedRate, + swap.fixedDayCount(), + swap.floatingSchedule(), + swap.iborIndex().clone(fwdTs_), + swap.spread, + swap.floatingDayCount(), + null); + exerciseDates_ = exerciseDates; + } + + public override double innerValue(FdmLinearOpIterator iter, double t) + { + Date iterExerciseDate = exerciseDates_.ContainsKey(t) ? exerciseDates_[t] : exerciseDates_.Last().Value; + + Vector disRate = getState(disModel_, t, iter); + Vector fwdRate = getState(fwdModel_, t, iter); + + if (disTs_.empty() || iterExerciseDate != disTs_.currentLink().referenceDate()) + { + Handle discount + = disModel_.termStructure(); + + disTs_.linkTo(new FdmAffineModelTermStructure(disRate, + discount.currentLink().calendar(), discount.currentLink().dayCounter(), + iterExerciseDate, discount.currentLink().referenceDate(), + disModel_)); + + Handle fwd = fwdModel_.termStructure(); + + fwdTs_.linkTo(new FdmAffineModelTermStructure(fwdRate, + fwd.currentLink().calendar(), fwd.currentLink().dayCounter(), + iterExerciseDate, fwd.currentLink().referenceDate(), + fwdModel_)); + + } + else { + (disTs_.currentLink() as FdmAffineModelTermStructure).setVariable(disRate); + (fwdTs_.currentLink() as FdmAffineModelTermStructure).setVariable(fwdRate); + } + + double npv = 0.0; + for (int j = 0; j < 2; j++) { + for (int i =0; i < swap_.leg(j).Count; ++i) { + npv += (swap_.leg(j)[i] as Coupon).accrualStartDate() >= iterExerciseDate + ? swap_.leg(j)[i].amount() * disTs_.currentLink().discount(swap_.leg(j)[i].date()) + : 0.0; + } + if (j == 0) + npv *= -1.0; + } + if (swap_.swapType == VanillaSwap.Type.Receiver) + npv *= -1.0; + + return Math.Max(0.0, npv); + } + public override double avgInnerValue(FdmLinearOpIterator iter, double t) + { + return innerValue(iter, t); + } + + Vector getState(ModelType model, double t, FdmLinearOpIterator iter) + { + if (model.GetType().Equals(typeof(HullWhite))) + { + Vector retVal = new Vector(1, (model as HullWhite).dynamics().shortRate(t, + mesher_.location(iter, direction_))); + return retVal; + } + else if (model.GetType().Equals(typeof(G2))) + { + Vector retVal = new Vector(2); + retVal[0] = mesher_.location(iter, direction_); + retVal[1] = mesher_.location(iter, direction_ + 1); + + return retVal; + } + else + return null; + } + + protected RelinkableHandle disTs_ = new RelinkableHandle(), fwdTs_ = new RelinkableHandle(); + protected ModelType disModel_, fwdModel_; + + protected IborIndex index_; + protected VanillaSwap swap_; + protected Dictionary exerciseDates_; + protected FdmMesher mesher_; + protected int direction_; + } +} diff --git a/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs new file mode 100644 index 000000000..ea6413cc5 --- /dev/null +++ b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs @@ -0,0 +1,59 @@ +/* + Copyright (C) 2017 Jean-Camille Tournier (jean-camille.tournier@avivainvestors.com) + + This file is part of QLNet Project https://github.com/amaggiulli/qlnet + + QLNet is free software: you can redistribute it and/or modify it + under the terms of the QLNet license. You should have received a + copy of the license along with this program; if not, license is + available online at . + + QLNet is a based on QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + The QuantLib license is available online at http://quantlib.org/license.shtml. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace QLNet +{ + public class FdmAffineModelTermStructure : YieldTermStructure + { + public FdmAffineModelTermStructure( + Vector r, + Calendar cal, + DayCounter dayCounter, + Date referenceDate, + Date modelReferenceDate, + IAffineModel model) + : base(referenceDate, cal, dayCounter) + { + r_ = r; + t_ = dayCounter.yearFraction(modelReferenceDate, referenceDate); + model_ = model; + model_.registerWith(update); + } + + public override Date maxDate() { return Date.maxDate(); } + public void setVariable(Vector r) + { + r_ = r; + notifyObservers(); + } + + protected override double discountImpl(double T) + { + return model_.discountBond(t_, T + t_, r_); + } + + protected Vector r_; + protected double t_; + protected IAffineModel model_; + } +} From c92776e9186201d3d4858b6bcfd074c04b08c7d7 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:26:48 +0200 Subject: [PATCH 07/15] 1 line was missing --- src/QLNet/Methods/Finitedifferences/finitedifferencemodel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/QLNet/Methods/Finitedifferences/finitedifferencemodel.cs b/src/QLNet/Methods/Finitedifferences/finitedifferencemodel.cs index d7bdd919a..31cfff9c4 100644 --- a/src/QLNet/Methods/Finitedifferences/finitedifferencemodel.cs +++ b/src/QLNet/Methods/Finitedifferences/finitedifferencemodel.cs @@ -67,6 +67,7 @@ private void rollbackImpl(ref object o, double from, double to, int steps, IStep } for (int i=0; i= 0 ; --j) { if (next <= stoppingTimes_[j] && stoppingTimes_[j] < now) { From 4a3ffec87f2c8b156da8bb73d51952c373f67768 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:27:44 +0200 Subject: [PATCH 08/15] Add HW swaption engine --- .../vanilla/FdHullWhiteSwaptionEngine.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs diff --git a/src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs b/src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs new file mode 100644 index 000000000..1d217f3d4 --- /dev/null +++ b/src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs @@ -0,0 +1,117 @@ +/* + Copyright (C) 2017 Jean-Camille Tournier (jean-camille.tournier@avivainvestors.com) + + This file is part of QLNet Project https://github.com/amaggiulli/qlnet + + QLNet is free software: you can redistribute it and/or modify it + under the terms of the QLNet license. You should have received a + copy of the license along with this program; if not, license is + available online at . + + QLNet is a based on QuantLib, a free-software/open-source library + for financial quantitative analysts and developers - http://quantlib.org/ + The QuantLib license is available online at http://quantlib.org/license.shtml. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the license for more details. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace QLNet +{ + public class FdHullWhiteSwaptionEngine : GenericModelEngine + { + public FdHullWhiteSwaptionEngine( + HullWhite model, + int tGrid = 100, int xGrid = 100, + int dampingSteps = 0, double invEps = 1e-5, + FdmSchemeDesc schemeDesc = null) + : base(model) + { + tGrid_ = tGrid; + xGrid_ = xGrid; + dampingSteps_ = dampingSteps; + schemeDesc_ = schemeDesc == null ? new FdmSchemeDesc().Douglas() : schemeDesc; + invEps_ = invEps; + } + + public override void calculate() { + // 1. Term structure + Handle ts = model_.currentLink().termStructure(); + + // 2. Mesher + DayCounter dc = ts.currentLink().dayCounter(); + Date referenceDate = ts.currentLink().referenceDate(); + double maturity = dc.yearFraction(referenceDate, + arguments_.exercise.lastDate()); + + + OrnsteinUhlenbeckProcess process = new OrnsteinUhlenbeckProcess(model_.currentLink().a(), model_.currentLink().sigma()); + + Fdm1dMesher shortRateMesher = + new FdmSimpleProcess1dMesher(xGrid_, process, maturity,1,invEps_); + + FdmMesher mesher = new FdmMesherComposite(shortRateMesher); + + // 3. Inner Value Calculator + List exerciseDates = arguments_.exercise.dates(); + Dictionary t2d = new Dictionary(); + + for (int i=0; i < exerciseDates.Count; ++i) { + double t = dc.yearFraction(referenceDate, exerciseDates[i]); + Utils.QL_REQUIRE(t >= 0, () => "exercise dates must not contain past date"); + + t2d.Add(t, exerciseDates[i]); + } + + Handle disTs = model_.currentLink().termStructure(); + Handle fwdTs + = arguments_.swap.iborIndex().forwardingTermStructure(); + + Utils.QL_REQUIRE(fwdTs.currentLink().dayCounter() == disTs.currentLink().dayCounter(), + () => "day counter of forward and discount curve must match"); + Utils.QL_REQUIRE(fwdTs.currentLink().referenceDate() == disTs.currentLink().referenceDate(), + () => "reference date of forward and discount curve must match"); + + HullWhite fwdModel = + new HullWhite(fwdTs, model_.currentLink().a(), model_.currentLink().sigma()); + + FdmInnerValueCalculator calculator = + new FdmAffineModelSwapInnerValue( + model_.currentLink(), fwdModel, + arguments_.swap, t2d, mesher, 0); + + // 4. Step conditions + FdmStepConditionComposite conditions = + FdmStepConditionComposite.vanillaComposite( + new DividendSchedule(), arguments_.exercise, + mesher, calculator, referenceDate, dc); + + // 5. Boundary conditions + FdmBoundaryConditionSet boundaries = new FdmBoundaryConditionSet(); + + // 6. Solver + FdmSolverDesc solverDesc = new FdmSolverDesc(); + solverDesc.mesher = mesher; + solverDesc.bcSet = boundaries; + solverDesc.condition = conditions; + solverDesc.calculator = calculator; + solverDesc.maturity = maturity; + solverDesc.timeSteps = tGrid_; + solverDesc.dampingSteps = dampingSteps_; + + FdmHullWhiteSolver solver = + new FdmHullWhiteSolver(model_, solverDesc, schemeDesc_); + + results_.value = solver.valueAt(0.0); + } + + protected int tGrid_, xGrid_, dampingSteps_; + protected FdmSchemeDesc schemeDesc_; + protected double invEps_; + } +} From 8a7dde3d2264964a904a0065c2221de7c151e77e Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:28:31 +0200 Subject: [PATCH 09/15] Update test case --- tests/QLNet.Tests/T_Bermudanswaption.cs | 56 +++++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/tests/QLNet.Tests/T_Bermudanswaption.cs b/tests/QLNet.Tests/T_Bermudanswaption.cs index b69dd87b2..44e0489c6 100644 --- a/tests/QLNet.Tests/T_Bermudanswaption.cs +++ b/tests/QLNet.Tests/T_Bermudanswaption.cs @@ -164,37 +164,57 @@ public void testCachedValues() { } Exercise exercise = new BermudanExercise(exerciseDates); - IPricingEngine engine = new TreeSwaptionEngine(model, 50); + IPricingEngine treeEngine = new TreeSwaptionEngine(model, 50); + IPricingEngine fdmEngine = new FdHullWhiteSwaptionEngine(model as HullWhite); #if QL_USE_INDEXED_COUPON - Real itmValue = 42.2413, atmValue = 12.8789, otmValue = 2.4759; - #else - double itmValue = 42.2470, atmValue = 12.8826, otmValue = 2.4769; + double itmValue = 42.2413, atmValue = 12.8789, otmValue = 2.4759; + double itmValueFdm = 42.2111, atmValueFdm = 12.8879, otmValueFdm = 2.44443; + #else + double itmValue = 42.2470, atmValue = 12.8826, otmValue = 2.4769; + double itmValueFdm = 42.2091, atmValueFdm = 12.8864, otmValueFdm = 2.4437; #endif double tolerance = 1.0e-4; Swaption swaption = new Swaption(itmSwap, exercise); - swaption.setPricingEngine(engine); + swaption.setPricingEngine(treeEngine); if (Math.Abs(swaption.NPV()-itmValue) > tolerance) QAssert.Fail("failed to reproduce cached in-the-money swaption value:\n" + "calculated: " + swaption.NPV() + "\n" - + "expected: " + itmValue); + + "expected: " + itmValue); + + swaption.setPricingEngine(fdmEngine); + if (Math.Abs(swaption.NPV() - itmValueFdm) > tolerance) + QAssert.Fail("failed to reproduce cached in-the-money swaption value:\n" + + "calculated: " + swaption.NPV() + "\n" + + "expected: " + itmValueFdm); - swaption = new Swaption(atmSwap, exercise); - swaption.setPricingEngine(engine); + swaption = new Swaption(atmSwap, exercise); + swaption.setPricingEngine(treeEngine); if (Math.Abs(swaption.NPV()-atmValue) > tolerance) QAssert.Fail("failed to reproduce cached at-the-money swaption value:\n" + "calculated: " + swaption.NPV() + "\n" - + "expected: " + atmValue); + + "expected: " + atmValue); + swaption.setPricingEngine(fdmEngine); + if (Math.Abs(swaption.NPV() - atmValueFdm) > tolerance) + QAssert.Fail("failed to reproduce cached at-the-money swaption value:\n" + + "calculated: " + swaption.NPV() + "\n" + + "expected: " + atmValueFdm); - swaption = new Swaption(otmSwap, exercise); - swaption.setPricingEngine(engine); + swaption = new Swaption(otmSwap, exercise); + swaption.setPricingEngine(treeEngine); if (Math.Abs(swaption.NPV()-otmValue) > tolerance) QAssert.Fail("failed to reproduce cached out-of-the-money " + "swaption value:\n" + "calculated: " + swaption.NPV() + "\n" - + "expected: " + otmValue); + + "expected: " + otmValue); + swaption.setPricingEngine(fdmEngine); + if (Math.Abs(swaption.NPV() - otmValueFdm) > tolerance) + QAssert.Fail("failed to reproduce cached out-of-the-money " + + "swaption value:\n" + + "calculated: " + swaption.NPV() + "\n" + + "expected: " + otmValueFdm); for (int j=0; j tolerance) QAssert.Fail("failed to reproduce cached in-the-money swaption value:\n" + "calculated: " + swaption.NPV() + "\n" + "expected: " + itmValue); - swaption = new Swaption(atmSwap, exercise); - swaption.setPricingEngine(engine); + swaption = new Swaption(atmSwap, exercise); + swaption.setPricingEngine(treeEngine); if (Math.Abs(swaption.NPV()-atmValue) > tolerance) QAssert.Fail("failed to reproduce cached at-the-money swaption value:\n" + "calculated: " + swaption.NPV() + "\n" + "expected: " + atmValue); - swaption = new Swaption(otmSwap, exercise); - swaption.setPricingEngine(engine); + swaption = new Swaption(otmSwap, exercise); + swaption.setPricingEngine(treeEngine); if (Math.Abs(swaption.NPV()-otmValue) > tolerance) QAssert.Fail("failed to reproduce cached out-of-the-money " + "swaption value:\n" From 71f929423b24dd49f21d97f6a783888f2427d3d9 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Thu, 26 Oct 2017 18:54:48 +0200 Subject: [PATCH 10/15] Update project file --- src/QLNet.Old/QLNet.Old.csproj | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/QLNet.Old/QLNet.Old.csproj b/src/QLNet.Old/QLNet.Old.csproj index a24206c8d..dff3ecb01 100644 --- a/src/QLNet.Old/QLNet.Old.csproj +++ b/src/QLNet.Old/QLNet.Old.csproj @@ -1005,6 +1005,9 @@ Methods\Finitedifferences\Meshers\Fdm1dMesher.cs + + Methods\Finitedifferences\Meshers\FdmSimpleProcess1dMesher.cs + Methods\Finitedifferences\Meshers\FdmBlackScholesMesher.cs @@ -1023,6 +1026,9 @@ Methods\Finitedifferences\Operators\FdmBlackScholesOp.cs + + Methods\Finitedifferences\Operators\FdmHullWhiteOp.cs + Methods\Finitedifferences\Operators\FdmLinearOp.cs @@ -1080,8 +1086,11 @@ Methods\Finitedifferences\Solvers\FdmBlackScholesSolver.cs + + Methods\Finitedifferences\Solvers\FdmHullWhiteSolver.cs + - Methods\Solvers\FdmSolverDesc.cs + Methods\Finitedifferences\Solvers\FdmSolverDesc.cs Methods\Finitedifferences\StepConditions\FdmAmericanStepCondition.cs @@ -1110,6 +1119,12 @@ Methods\Finitedifferences\Utilities\FdmInnerValueCalculator.cs + + Methods\Finitedifferences\Utilities\FdmAffineModelSwapInnerValue.cs + + + Methods\Finitedifferences\Utilities\FdmAffineModelTermStructure.cs + Methods\Finitedifferences\Utilities\FdmMesherIntegral.cs @@ -1478,6 +1493,9 @@ Pricingengines\vanilla\FdBlackScholesVanillaEngine.cs + + Pricingengines\vanilla\FdHullWhiteSwaptionEngine.cs + Pricingengines\vanilla\FDAmericanEngine.cs From a9ff37f70198f00b0b1a561a4014959979bb8670 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Fri, 27 Oct 2017 09:09:19 +0200 Subject: [PATCH 11/15] Fix SonarQube --- .../Utilities/FdmAffineModelSwapInnerValue.cs | 4 ++-- .../Utilities/FdmAffineModelTermStructure.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs index 4309b459f..15d1c5836 100644 --- a/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs +++ b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelSwapInnerValue.cs @@ -100,7 +100,7 @@ public override double avgInnerValue(FdmLinearOpIterator iter, double t) return innerValue(iter, t); } - Vector getState(ModelType model, double t, FdmLinearOpIterator iter) + public Vector getState(ModelType model, double t, FdmLinearOpIterator iter) { if (model.GetType().Equals(typeof(HullWhite))) { @@ -117,7 +117,7 @@ Vector getState(ModelType model, double t, FdmLinearOpIterator iter) return retVal; } else - return null; + return new Vector(); } protected RelinkableHandle disTs_ = new RelinkableHandle(), fwdTs_ = new RelinkableHandle(); diff --git a/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs index ea6413cc5..0b2f63f23 100644 --- a/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs +++ b/src/QLNet/Methods/Finitedifferences/Utilities/FdmAffineModelTermStructure.cs @@ -47,9 +47,9 @@ public void setVariable(Vector r) notifyObservers(); } - protected override double discountImpl(double T) + protected override double discountImpl(double d) { - return model_.discountBond(t_, T + t_, r_); + return model_.discountBond(t_, d + t_, r_); } protected Vector r_; From 243e81d3aefff54c9495af5d641c8d1d5303df0e Mon Sep 17 00:00:00 2001 From: tournierjc Date: Fri, 27 Oct 2017 09:09:53 +0200 Subject: [PATCH 12/15] Fix AppVeyor --- .../StepConditions/FdmBermudanStepCondition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs b/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs index d4a315f88..79fa1eb8b 100644 --- a/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs +++ b/src/QLNet/Methods/Finitedifferences/StepConditions/FdmBermudanStepCondition.cs @@ -46,7 +46,7 @@ public FdmBermudanStepCondition(List exerciseDates, public void applyTo(object o, double t) { Vector a = (Vector) o; - if (exerciseTimes_.BinarySearch(t) > 0) + if (exerciseTimes_.BinarySearch(t) >= 0) { FdmLinearOpLayout layout = mesher_.layout(); FdmLinearOpIterator endIter = layout.end(); From 66c5b10998ba21a5d929bf607e915ae30ddc0ccb Mon Sep 17 00:00:00 2001 From: tournierjc Date: Fri, 27 Oct 2017 09:10:30 +0200 Subject: [PATCH 13/15] Fix SonarQube --- .../Finitedifferences/Operators/FdmHullWhiteOp.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs b/src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs index 46914f197..1f4edc727 100644 --- a/src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs +++ b/src/QLNet/Methods/Finitedifferences/Operators/FdmHullWhiteOp.cs @@ -66,15 +66,15 @@ public override Vector apply_direction(int direction, Vector r) { return retVal; } } - public override Vector solve_splitting(int direction, Vector r, double dt) { + public override Vector solve_splitting(int direction, Vector r, double s) { if (direction == direction_) - return mapT_.solve_splitting(r, dt, 1.0); + return mapT_.solve_splitting(r, s, 1.0); else { Vector retVal = new Vector(r.size(), 0.0); return retVal; } } - public override Vector preconditioner(Vector r, double dt) { return solve_splitting(direction_, r, dt); } + public override Vector preconditioner(Vector r, double s) { return solve_splitting(direction_, r, s); } public override List toMatrixDecomp() { @@ -84,8 +84,8 @@ public override List toMatrixDecomp() #region IOperator interface public override IOperator identity(int size) { return null; } - public override Vector applyTo(Vector v) { return null; } - public override Vector solveFor(Vector rhs) { return null; } + public override Vector applyTo(Vector v) { return new Vector(); } + public override Vector solveFor(Vector rhs) { return new Vector(); } public override IOperator multiply(double a, IOperator D) { return null; } public override IOperator add(IOperator A, IOperator B) { return null; } From 406f84ea86f2a0d6703b4743636952607b78c5a4 Mon Sep 17 00:00:00 2001 From: tournierjc Date: Fri, 27 Oct 2017 09:13:19 +0200 Subject: [PATCH 14/15] Fix SonarQube --- .../Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs b/src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs index b52eb4d1c..3774802ef 100644 --- a/src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs +++ b/src/QLNet/Methods/Finitedifferences/Meshers/FdmSimpleProcess1dMesher.cs @@ -24,10 +24,10 @@ namespace QLNet { /// /// One-dimensional grid mesher - /// - public class FdmSimpleProcess1dMesher : Fdm1dMesher + /// + public class FdmSimpleProcess1DMesher : Fdm1dMesher { - public FdmSimpleProcess1dMesher(int size, + public FdmSimpleProcess1DMesher(int size, StochasticProcess1D process, double maturity, int tAvgSteps = 10, double epsilon = 0.0001, @@ -59,7 +59,7 @@ public FdmSimpleProcess1dMesher(int size, locations_[i] += process.evolve(0, process.x0(), t, new InverseCumulativeNormal().value(p)); } - locations_[locations_.Count() - 1] += qMax; + locations_[locations_.Count - 1] += qMax; } locations_ = locations_.Select(x => x / tAvgSteps).ToList(); for (int i=0; i < size-1; ++i) From 33c7ab3ed0019e0c641718cddeb2d0e96643f8dd Mon Sep 17 00:00:00 2001 From: tournierjc Date: Fri, 27 Oct 2017 09:13:44 +0200 Subject: [PATCH 15/15] Add files via upload --- src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs b/src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs index 1d217f3d4..b03436402 100644 --- a/src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs +++ b/src/QLNet/Pricingengines/vanilla/FdHullWhiteSwaptionEngine.cs @@ -52,8 +52,8 @@ public override void calculate() { OrnsteinUhlenbeckProcess process = new OrnsteinUhlenbeckProcess(model_.currentLink().a(), model_.currentLink().sigma()); - Fdm1dMesher shortRateMesher = - new FdmSimpleProcess1dMesher(xGrid_, process, maturity,1,invEps_); + Fdm1dMesher shortRateMesher = + new FdmSimpleProcess1DMesher(xGrid_, process, maturity, 1, invEps_); FdmMesher mesher = new FdmMesherComposite(shortRateMesher);