diff --git a/.gitignore b/.gitignore index 0a9e78f0e..4a295e963 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ TestResults obj bin +.vs *.user *.suo *.tss diff --git a/ChangeLog.txt b/ChangeLog.txt index a026ea312..79cd4ba1d 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,717 +1,522 @@ -commit e2dfdf4864dcf8c84a7cf7827e283c7b181d49d6 +commit d7536399ebac07f40ab396a5756398de75f06535 Author: Andrea Maggiulli -Date: Fri Sep 2 11:15:43 2016 +0200 +Date: Thu Jan 19 10:51:16 2017 +0100 - Avoid Parallelization in xunit tests . Now you can run : dotnet test to launch whole suite. + Assembly version updated to 1.9.0.0 - Test/Properties/AssemblyInfo.cs | 5 +++++ - 1 file changed, 5 insertions(+) + Examples/BermudanSwaption/Properties/AssemblyInfo.cs | 6 +++--- + Examples/Bonds/Properties/AssemblyInfo.cs | 6 +++--- + Examples/CVAIRS/Properties/AssemblyInfo.cs | 6 +++--- + Examples/CallableBonds/Properties/AssemblyInfo.cs | 6 +++--- + Examples/EquityOption/Properties/AssemblyInfo.cs | 6 +++--- + Examples/FRA/Properties/AssemblyInfo.cs | 6 +++--- + Examples/FittedBondCurve/Properties/AssemblyInfo.cs | 6 +++--- + Examples/Repo/Properties/AssemblyInfo.cs | 6 +++--- + Examples/Swap/Properties/AssemblyInfo.cs | 6 +++--- + QLNet/Properties/AssemblyInfo.cs | 6 +++--- + Test/Properties/AssemblyInfo.cs | 6 +++--- + 11 files changed, 33 insertions(+), 33 deletions(-) -commit da52b15ba4b736e238429b3d3fb3e37f6f02f88b +commit 40dfdf8e9e286fa9a8ebce9473e31c8dd352b691 Author: Andrea Maggiulli -Date: Thu Sep 1 16:31:20 2016 +0200 +Date: Tue Jan 17 17:51:08 2017 +0100 - Small trick to fix AppVeyor .NET compilation + Fix Empty redundant statement. - QLNet/qlnet.project.json | 7 +++++++ - Test/test.project.json | 7 +++++++ - 2 files changed, 14 insertions(+) + Test/T_BlackDeltaCalculator.cs | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) -commit 0f8eef4e1b5e55c15131a19069ab8340291538f5 +commit 9d3ecd117391a05e8bc127332ee22df0b3a754d8 Author: Andrea Maggiulli -Date: Thu Sep 1 15:48:55 2016 +0200 +Date: Tue Jan 17 17:45:05 2017 +0100 - Updating code for .NET Core compatibility : Visual Studio 2015 project for .net core compilation. - Now you can compile and test QLNet under .net core from cli or visual studio 2015 ! Enjoy :) + Fix inconsistent modifiers declaration order - QLNet/QLNet.xproj | 19 + - QLNet/project.json | 13 + - QLNet/project.lock.json | 2506 ++++++++++++++++ - QLNet_Core.sln | 39 + - Test/Test.xproj | 22 + - Test/project.json | 20 + - Test/project.lock.json | 7496 +++++++++++++++++++++++++++++++++++++++++++++++ - global.json | 6 + - 8 files changed, 10121 insertions(+) + QLNet/Models/Parameter.cs | 6 +++--- + QLNet/StochasticProcess.cs | 2 +- + 2 files changed, 4 insertions(+), 4 deletions(-) -commit 7dc5202a86229bda2934cbdb9b04d958398a30b1 +commit 7b655e21afaf09736d419fac5a146283551852c0 Author: Andrea Maggiulli -Date: Thu Sep 1 15:46:09 2016 +0200 +Date: Tue Jan 17 15:27:12 2017 +0100 - Updating code for .NET Core compatibility : Small ctor fix + List and InitializedList refactoring : base class is always a List , InitializedList is an utility to construct a initialized list. - QLNet/Math/Interpolations/MixedInterpolation.cs | 4 ++-- + QLNet/Extensions/ListExtension.cs | 7 +++++ + QLNet/Instruments/Bonds/BTP.cs | 12 ++++---- + QLNet/Instruments/Loan.cs | 7 +++-- + QLNet/Instruments/Swap.cs | 32 ++++++++++++++++------ + QLNet/Math/Interpolations/CubicInterpolation.cs | 4 +-- + QLNet/Math/Interpolations/Linearinterpolation.cs | 2 +- + QLNet/Math/Optimization/Simplex.cs | 5 ++-- + .../Shortrate/calibrationhelpers/caphelper.cs | 2 +- + .../inflation/InflationCapFloorEngines.cs | 7 +++-- + Test/T_Bonds.cs | 12 ++++---- + Test/T_InflationCapFlooredCouponTest.cs | 14 +++++----- + 11 files changed, 66 insertions(+), 38 deletions(-) + +commit 89158a3c8688d654bce232d3c129286267ee5ad4 +Author: Andrea Maggiulli +Date: Tue Jan 17 12:18:17 2017 +0100 + + Sorted modifiers. + + QLNet/Instruments/AsianOption.cs | 8 ++++---- + QLNet/Instruments/BarrierOption.cs | 4 ++-- + QLNet/Instruments/BasisSwap.cs | 2 +- + QLNet/Instruments/DividendVanillaOption.cs | 4 ++-- + QLNet/Instruments/MultiAssetOption.cs | 2 +- + QLNet/Instruments/OneAssetOption.cs | 2 +- + QLNet/Instruments/VanillaSwap.cs | 2 +- + 7 files changed, 12 insertions(+), 12 deletions(-) + +commit 1c30f44c4f6d16800fa4125c6b8af368cc7e497d +Merge: cbe01ab b0fce11 +Author: Andrea Maggiulli +Date: Fri Jan 13 14:57:49 2017 +0100 + + Merge pull request #116 from jiskin/BermudeanSwaption-fix + + Issue with times in the TimeGrid + +commit b0fce11897ae56022837be08046188522b52abfb +Author: Andrea Maggiulli +Date: Fri Jan 13 14:51:47 2017 +0100 + + Fix BermudaSwaption example + + Grid construction is wrong yes , this give also correct numbers. + + Examples/BermudanSwaption/BermudanSwaption.cs | 592 +++++++++++++------------- + 1 file changed, 296 insertions(+), 296 deletions(-) + +commit eec9f49481446084d0ce43be14ac6735e6c1748a +Author: jiskin +Date: Fri Jan 13 14:03:17 2017 +0100 + + Issue with times in the TimeGrid + + Not sure this is the expected results, but at least the example is running + + Examples/BermudanSwaption/BermudanSwaption.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) -commit b9ff67553a423f5bea80b0d775608b52699687f3 -Author: Andrea Maggiulli -Date: Thu Sep 1 15:45:30 2016 +0200 - - Updating code for .NET Core compatibility : Removed unused files. - - .../Interpolations/CubicSplineInterpolation.cs | 341 --------------------- - Test/T_Loan.cs | 69 ----- - 2 files changed, 410 deletions(-) - -commit 1203723cb824ba48faa8873aa50c5034a2cab3f1 -Author: Andrea Maggiulli -Date: Thu Sep 1 14:28:23 2016 +0200 - - Updating code for .NET Core compatibility : Refactored Test project and code to work with Microsoft UnitTesting and Xunit. - Now test project is runnable with .net core. - - Test/T_AmericanOption.cs | 43 ++- - Test/T_AsianOptions.cs | 48 +++- - Test/T_AssetSwap.cs | 367 ++++++++++++++----------- - Test/T_BasketOption.cs | 196 +++++++------- - Test/T_Bermudanswaption.cs | 69 +++-- - Test/T_BlackFormula.cs | 24 +- - Test/T_Bonds.cs | 195 +++++++++----- - Test/T_BusinessDayConvention.cs | 54 ++-- - Test/T_Calendars.cs | 388 ++++++++++++++++----------- - Test/T_CapFloor.cs | 141 ++++++---- - Test/T_CashFlows.cs | 40 ++- - Test/T_CreditDefaultSwap.cs | 64 +++-- - Test/T_Dates.cs | 126 +++++---- - Test/T_DayCounters.cs | 68 +++-- - Test/T_DefaultProbabilityCurves.cs | 50 ++-- - Test/T_DigitalOption.cs | 56 +++- - Test/T_DividendOption.cs | 58 +++- - Test/T_EuropeanOption.cs | 121 ++++++--- - Test/T_ExchangeRate.cs | 94 ++++--- - Test/T_Functions.cs | 88 +++--- - Test/T_HestonModel.cs | 124 ++++++--- - Test/T_HybridHestonHullWhiteProcess.cs | 152 +++++++---- - Test/T_Inflation.cs | 76 ++++-- - Test/T_InflationCapFloorTest.cs | 65 +++-- - Test/T_InflationCapFlooredCouponTest.cs | 65 +++-- - Test/T_Instruments.cs | 32 ++- - Test/T_InterestRate.cs | 48 ++-- - Test/T_Interpolations.cs | 312 +++++++++++++-------- - Test/T_LiborMarketModel.cs | 87 ++++-- - Test/T_LiborMarketModelProcess.cs | 82 ++++-- - Test/T_LinearLeastSquaresRegression.cs | 95 ++++--- - Test/T_LowDiscrepancySequences.cs | 98 ++++--- - Test/T_Matrices.cs | 236 +++++++++------- - Test/T_Money.cs | 50 ++-- - Test/T_Operators.cs | 42 ++- - Test/T_Optimizers.cs | 36 ++- - Test/T_OptionletStripper.cs | 216 ++++++++------- - Test/T_OvernightIndexedSwap.cs | 98 ++++--- - Test/T_PSACurve.cs | 26 +- - Test/T_PathGenerator.cs | 76 ++++-- - Test/T_PiecewiseZeroSpreadedTermStructure.cs | 122 ++++++--- - Test/T_Piecewiseyieldcurve.cs | 153 ++++++++--- - Test/T_Quotes.cs | 68 +++-- - Test/T_RNGTraits.cs | 48 ++-- - Test/T_RiskStats.cs | 116 ++++---- - Test/T_Rounding.cs | 74 +++-- - Test/T_SampledCurve.cs | 36 ++- - Test/T_Schedule.cs | 88 ++++-- - Test/T_ShortRateModels.cs | 50 ++-- - Test/T_Solvers.cs | 68 +++-- - Test/T_Stats.cs | 162 ++++++----- - Test/T_Swaps.cs | 119 +++++--- - Test/T_Swaption.cs | 135 ++++++---- - Test/T_SwaptionVolatilitymatrix.cs | 91 ++++--- - Test/T_TermStructures.cs | 135 ++++++---- - Test/Test.csproj | 4 +- - Test/Utilities.cs | 79 +++++- - 57 files changed, 3823 insertions(+), 2031 deletions(-) - -commit 2977a094c388c58f80100c8c7c76b48a3d008c00 -Author: Andrea Maggiulli -Date: Wed Aug 31 15:04:14 2016 +0200 - - Updating code for .NET Core compatibility : WeakEventSource refactoring, added ICloneable interface if .net core - - QLNet/Patterns/WeakEventSource.cs | 19 +++++++++++++++---- - QLNet/Utils.cs | 9 ++++++++- - 2 files changed, 23 insertions(+), 5 deletions(-) - -commit eb848b6655914cca4baa5bc0127797b5466a898e -Author: Andrea Maggiulli -Date: Wed Aug 31 14:30:13 2016 +0200 - - Updating code for .NET Core compatibility : ForEach Action refactoring - - QLNet/Instruments/YearOnYearInflationSwap.cs | 2 +- - QLNet/Instruments/ZeroCouponInflationSwap.cs | 2 +- - QLNet/Termstructures/Inflation/PiecewiseYoYInflationCurve.cs | 2 +- - QLNet/Termstructures/Inflation/PiecewiseZeroInflationCurve.cs | 2 +- - QLNet/Termstructures/Iterativebootstrap.cs | 2 +- - QLNet/Termstructures/Yield/PiecewiseYieldCurve.cs | 2 +- - QLNet/Termstructures/localbootstrap.cs | 4 ++-- - 7 files changed, 8 insertions(+), 8 deletions(-) - -commit 0ad3e16f01c10f8dc13ad39491cc36a5c363f9b8 -Author: Andrea Maggiulli -Date: Wed Aug 31 12:47:55 2016 +0200 - - Updating code for .NET Core compatibility : Defined QL_DOTNET_FRAMEWORK for debug and release - - QLNet/QLNet.csproj | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) +commit cbe01abeb181e3cc54759b240bb48ffac38af764 +Merge: f0fa658 213b554 +Author: Andrea Maggiulli +Date: Fri Jan 13 10:44:06 2017 +0100 + + Fix Issue #115 -commit b86f412d0e4303a87baab80ea09f9f12e7855bb6 +commit 213b554f5d1f3bfe53bc198e2ac799a43538b21f Author: Andrea Maggiulli -Date: Wed Aug 31 12:39:35 2016 +0200 +Date: Fri Jan 13 10:43:14 2017 +0100 - Updating code for .NET Core compatibility : Defined QL_DOTNET_FRAMEWORK compilation constant to identify between .net framework and .net core. - Encapsulated GetMethodInfo in a static function to handle the environments differences - Small string fix. + Fix Issue #115 - QLNet/Cashflows/CashFlows.cs | 9 +++++---- - QLNet/Cashflows/CouponPricer.cs | 2 +- - QLNet/Math/statistics/sequencestatistics.cs | 8 ++++---- - QLNet/Pricingengines/BlackCalculator.cs | 4 ++-- - QLNet/QLNet.csproj | 2 +- - QLNet/Time/Imm.cs | 4 ++-- - QLNet/Utils.cs | 16 +++++++++++++++- - 7 files changed, 30 insertions(+), 15 deletions(-) + .../asian/mc_discr_arith_av_price.cs | 12 ++-- + Test/T_AsianOptions.cs | 68 ++++++++++++++++++++++ + 2 files changed, 74 insertions(+), 6 deletions(-) -commit 37dab31f62821aa344010a8525df58d587259d5d +commit f0fa658c382639d320b8b03c9eb5b7045f951234 +Merge: e7f3c79 32e83c7 Author: Andrea Maggiulli -Date: Wed Aug 31 11:37:28 2016 +0200 +Date: Thu Jan 5 12:25:17 2017 +0100 - Updating code for .NET Core compatibility : ToLongDateString and ToShortDateString refactoring. + Merge branch 'develop' of https://github.com/amaggiulli/qlnet into develop - QLNet/Time/Date.cs | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) +commit e7f3c794292ba52a9fee64d2dde4fd12cefcb24f +Author: Andrea Maggiulli +Date: Thu Jan 5 12:24:55 2017 +0100 -commit d741c8e8e332aa84ae0fb7c15a13c02dd88a91bb -Author: Andrea Maggiulli -Date: Wed Aug 31 11:17:23 2016 +0200 - - Updating code for .NET Core compatibility : ApplicationException become Exception. - - QLNet/Cashflows/CPICoupon.cs | 14 +- - QLNet/Cashflows/CappedFlooredCoupon.cs | 10 +- - QLNet/Cashflows/CappedFlooredYoYInflationCoupon.cs | 2 +- - QLNet/Cashflows/Cashflowvectors.cs | 18 +- - QLNet/Cashflows/ConundrumPricer.cs | 12 +- - QLNet/Cashflows/DigitalCoupon.cs | 36 +-- - QLNet/Cashflows/Dividend.cs | 4 +- - QLNet/Cashflows/InflationCoupon.cs | 6 +- - QLNet/Cashflows/InflationCouponPricer.cs | 6 +- - QLNet/Cashflows/OvernightIndexedCoupon.cs | 16 +- - QLNet/Cashflows/SimpleCashFlow.cs | 2 +- - QLNet/Cashflows/averagebmacoupon.cs | 30 +-- - QLNet/Error.cs | 4 +- - QLNet/Exercise.cs | 4 +- - QLNet/Handle.cs | 2 +- - QLNet/Indexes/Ibor/Euribor.cs | 4 +- - QLNet/Indexes/Ibor/Eurlibor.cs | 78 +++--- - QLNet/Indexes/Ibor/Libor.cs | 68 +++--- - QLNet/Indexes/InflationIndex.cs | 140 +++++------ - QLNet/Instruments/AsianOption.cs | 20 +- - QLNet/Instruments/BarrierOption.cs | 18 +- - QLNet/Instruments/Bonds/AmortizingFixedRateBond.cs | 4 +- - QLNet/Instruments/Bonds/CmsRateBond.cs | 4 +- - QLNet/Instruments/Bonds/Fixedratebond.cs | 16 +- - QLNet/Instruments/Bonds/FloatingRateBond.cs | 12 +- - QLNet/Instruments/DividendVanillaOption.cs | 8 +- - QLNet/Instruments/ImpliedVolatility.cs | 2 +- - QLNet/Instruments/MultiAssetOption.cs | 16 +- - QLNet/Instruments/OneAssetOption.cs | 24 +- - QLNet/Instruments/OvernightIndexedSwap.cs | 10 +- - QLNet/Instruments/Stock.cs | 2 +- - QLNet/Instruments/VanillaSwap.cs | 6 +- - QLNet/Instruments/bmaswap.cs | 12 +- - QLNet/Instruments/forward.cs | 6 +- - QLNet/Instruments/forwardrateagreement.cs | 2 +- - QLNet/Instruments/payoffs.cs | 6 +- - QLNet/InterestRate.cs | 6 +- - QLNet/Math/Distributions/GammaDistribution.cs | 6 +- - QLNet/Math/Distributions/NormalDistribution.cs | 12 +- - QLNet/Math/Distributions/binomialdistribution.cs | 12 +- - QLNet/Math/Distributions/chisquaredistribution.cs | 2 +- - QLNet/Math/Distributions/poissondistribution.cs | 4 +- - .../Interpolations/convexmonotoneinterpolation.cs | 10 +- - QLNet/Math/Matrix.cs | 28 +-- - QLNet/Math/Optimization/Constraint.cs | 262 ++++++++++----------- - QLNet/Math/Optimization/EndCriteria.cs | 4 +- - QLNet/Math/Optimization/LineSearch.cs | 2 +- - QLNet/Math/Optimization/ProjectedCostFunction.cs | 10 +- - QLNet/Math/Optimization/Simplex.cs | 188 +++++++-------- - QLNet/Math/Optimization/levenbergmarquardt.cs | 8 +- - QLNet/Math/SampledCurve.cs | 6 +- - QLNet/Math/Solvers1d/Newton.cs | 2 +- - QLNet/Math/Solvers1d/Newtonsafe.cs | 2 +- - QLNet/Math/Vector.cs | 18 +- - QLNet/Math/beta.cs | 8 +- - QLNet/Math/integrals/Integral.cs | 2 +- - QLNet/Math/integrals/Segmentintegral.cs | 2 +- - .../Math/integrals/gaussianorthogonalpolynomial.cs | 18 +- - QLNet/Math/integrals/simpsonintegral.cs | 2 +- - QLNet/Math/integrals/trapezoidintegral.cs | 2 +- - QLNet/Math/linearleastsquaresregression.cs | 4 +- - .../Math/matrixutilities/choleskydecomposition.cs | 4 +- - QLNet/Math/matrixutilities/pseudosqrt.cs | 26 +- - .../matrixutilities/symmetricschurdecomposition.cs | 6 +- - .../Math/randomnumbers/randomsequencegenerator.cs | 2 +- - QLNet/Math/randomnumbers/sobolrsg.cs | 212 ++++++++--------- - QLNet/Math/statistics/gaussianstatistics.cs | 8 +- - QLNet/Math/statistics/generalstatistics.cs | 22 +- - QLNet/Math/statistics/incrementalstatistics.cs | 26 +- - QLNet/Math/statistics/riskstatistics.cs | 16 +- - QLNet/Math/statistics/sequencestatistics.cs | 8 +- - .../Finitedifferences/TridiagonalOperator.cs | 18 +- - .../Finitedifferences/finitedifferencemodel.cs | 2 +- - QLNet/Methods/lattices/binominaltree.cs | 16 +- - QLNet/Methods/lattices/lattice.cs | 4 +- - QLNet/Methods/montecarlo/brownianbridge.cs | 4 +- - QLNet/Methods/montecarlo/lsmbasissystem.cs | 2 +- - QLNet/Methods/montecarlo/multipath.cs | 2 +- - QLNet/Methods/montecarlo/multipathgenerator.cs | 6 +- - QLNet/Methods/montecarlo/path.cs | 24 +- - QLNet/Methods/montecarlo/pathgenerator.cs | 4 +- - QLNet/Models/Parameter.cs | 44 ++-- - .../Shortrate/Onefactormodels/coxingersollross.cs | 4 +- - .../Models/Shortrate/Onefactormodels/hullwhite.cs | 10 +- - QLNet/Models/Shortrate/Twofactorsmodels/g2.cs | 2 +- - QLNet/Models/model.cs | 4 +- - QLNet/Option.cs | 6 +- - QLNet/Pricingengines/Americanpayoffathit.cs | 14 +- - QLNet/Pricingengines/BlackCalculator.cs | 18 +- - QLNet/Pricingengines/Blackscholescalculator.cs | 4 +- - .../asian/mc_discr_arith_av_price.cs | 18 +- - .../asian/mc_discr_arith_av_strike.cs | 8 +- - .../Pricingengines/asian/mc_discr_geom_av_price.cs | 12 +- - .../Pricingengines/asian/mcdiscreteasianengine.cs | 2 +- - .../barrier/AnalyticBarrierEngine.cs | 12 +- - QLNet/Pricingengines/mclongstaffschwartzengine.cs | 2 +- - .../Pricingengines/swaption/blackswaptionengine.cs | 2 +- - QLNet/Pricingengines/swaption/g2swaptionengine.cs | 2 +- - .../vanilla/AnalyticEuropeanEngine.cs | 6 +- - QLNet/Pricingengines/vanilla/FDDividendEngine.cs | 2 +- - .../Pricingengines/vanilla/FDMultiPeriodEngine.cs | 8 +- - .../vanilla/FDStepConditionEngine.cs | 2 +- - QLNet/Pricingengines/vanilla/FDVanillaEngine.cs | 4 +- - QLNet/Pricingengines/vanilla/Integralengine.cs | 4 +- - QLNet/Pricingengines/vanilla/Juquadraticengine.cs | 10 +- - .../vanilla/baroneadesiwhaleyengine.cs | 12 +- - QLNet/Pricingengines/vanilla/binomialengine.cs | 10 +- - .../vanilla/bjerksundstenslandengine.cs | 12 +- - .../vanilla/discretizedvanillaoption.cs | 2 +- - QLNet/Pricingengines/vanilla/mcamericanengine.cs | 16 +- - QLNet/Pricingengines/vanilla/mcvanillaengine.cs | 48 ++-- - QLNet/Termstructures/Inflation/InflationHelpers.cs | 2 +- - QLNet/Termstructures/Inflation/Seasonality.cs | 10 +- - QLNet/Termstructures/InflationTermStructure.cs | 8 +- - .../Volatility/Inflation/CPIVolatilitySurface.cs | 2 +- - .../yoyinflationoptionletvolatilitystructure.cs | 14 +- - QLNet/Termstructures/Volatility/Sabr.cs | 14 +- - QLNet/Termstructures/Volatility/SmileSection.cs | 4 +- - .../Volatility/equityfx/BlackVarianceCurve.cs | 8 +- - .../Volatility/equityfx/LocalVolSurface.cs | 8 +- - QLNet/Termstructures/localbootstrap.cs | 4 +- - QLNet/Time/Calendars/JointCalendar.cs | 6 +- - QLNet/Time/Calendars/Ukraine.cs | 2 +- - QLNet/Time/Calendars/brazil.cs | 10 +- - QLNet/Time/Period.cs | 20 +- - QLNet/Time/Schedule.cs | 6 +- - QLNet/Utils.cs | 4 +- - QLNet/discretizedasset.cs | 4 +- - QLNet/legacy/libormarketmodels/lfmcovarproxy.cs | 2 +- - .../legacy/libormarketmodels/lfmhullwhiteparam.cs | 6 +- - .../legacy/libormarketmodels/lfmswaptionengine.cs | 2 +- - .../legacy/libormarketmodels/liborforwardmodel.cs | 6 +- - QLNet/legacy/libormarketmodels/lmfixedvolmodel.cs | 10 +- - QLNet/payoff.cs | 2 +- - QLNet/processes/Ornsteinuhlenbeckprocess.cs | 52 ++-- - QLNet/processes/stochasticprocessarray.cs | 4 +- - QLNet/timegrid.cs | 12 +- - Test/T_Interpolations.cs | 4 +- - Test/T_Mclongstaffschwartzengine.cs | 6 +- - Test/T_Optimizers.cs | 46 ++-- - 140 files changed, 1117 insertions(+), 1117 deletions(-) - -commit cc4ef00defa92555df38c03facbc29bfb7f577c9 -Author: Andrea Maggiulli -Date: Mon Aug 29 12:32:34 2016 +0200 - - Fix Date.daysBetween and Date operator. Thx @Askolein and @igitur (#106) - - QLNet/Time/Date.cs | 5 ++--- - Test/T_Dates.cs | 7 ++++++- - 2 files changed, 8 insertions(+), 4 deletions(-) - -commit c80eff8f6058a4e85fa51aff7b0eb5397f93d6fa -Author: Francois Botha -Date: Wed Jul 27 16:20:38 2016 +0200 - - Initialise empty convexity adjustment if null (#101) - - QLNet/Termstructures/Yield/Ratehelpers.cs | 2 ++ - 1 file changed, 2 insertions(+) - -commit 2e80773a7773ca9caf5387a488966d628e8ef60c -Merge: a98e679 c5773c5 + Added CPISwap with tests. + + QLNet/Instruments/CPISwap.cs | 327 +++++++++++++++++++++++++++ + QLNet/QLNet.csproj | 1 + + Test/T_CPISwap.cs | 517 +++++++++++++++++++++++++++++++++++++++++++ + Test/Test.csproj | 1 + + 4 files changed, 846 insertions(+) + +commit 15c40dfae7af76dfd8982f3e2fa8e8b18dd3421f +Author: Andrea Maggiulli +Date: Thu Jan 5 12:15:28 2017 +0100 + + BUGFIX : PricingEngineResults copy reference list instead of duplicate data. + This fix problems when pricing different Swaps with same engine loosing previous calculation. + + QLNet/Instruments/Instrument.cs | 2 +- + QLNet/Instruments/Loan.cs | 8 ++++---- + QLNet/Instruments/Swap.cs | 20 ++++++++++---------- + 3 files changed, 15 insertions(+), 15 deletions(-) + +commit 32e83c75392b0df343df8bb1489f5a4cd21b8172 +Merge: 0197f33 3a1c06f Author: Andrea Maggiulli -Date: Tue Jul 12 18:04:57 2016 +0200 +Date: Wed Jan 4 17:14:39 2017 +0100 - Merge pull request #95 from screig/develop + Merge pull request #113 from jiskin/develop - Following the convention of Quant lib, default is to allow negative rates + Cast issue in TreeCallableBondEngine -commit c5773c511bff4f10991243f0d9e60b9cdafc3c1d -Author: sean creighton -Date: Tue Jul 12 16:51:48 2016 +0200 +commit 0197f3300e08f68a0e93bcb2dacadd5813d8a162 +Author: Andrea Maggiulli +Date: Wed Jan 4 17:10:50 2017 +0100 + + Added CPICapFloor with engine and tests. + + QLNet/Instruments/CPICapFloor.cs | 178 +++++++++ + .../inflation/InterpolatingCPICapFloorEngine.cs | 111 ++++++ + QLNet/QLNet.csproj | 3 + + .../Inflation/CPICapFloorTermPriceSurface.cs | 346 ++++++++++++++++ + Test/T_InflationCPICapFloor.cs | 444 +++++++++++++++++++++ + Test/Test.csproj | 1 + + 6 files changed, 1083 insertions(+) - Following the convention of Quant lib, default is to allow negative rates +commit 3a1c06f85d987504ce17228a58a6f9bb126a91d4 +Author: jiskin +Date: Wed Jan 4 16:20:49 2017 +0100 + + Add files via upload + + The Callable Bond example was raising an issue. - QLNet/QLNet.csproj | 2 +- + QLNet/Pricingengines/Bond/TreeCallableBondEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) -commit a98e679f703bd3a265cac266a8e854524481df3b -Merge: c0a1463 13d4582 -Author: Andrea Maggiulli -Date: Tue Jul 12 16:12:58 2016 +0200 +commit e6686b894d3d1ed9a68ba9531c7533825d567ed8 +Merge: d1e3f65 9dde204 +Author: jiskin +Date: Wed Dec 28 11:10:35 2016 +0100 - Merge pull request #92 from s-moon/patch-1 + Merge pull request #1 from amaggiulli/develop - Minor edits + integrate amaggiulli changes -commit c0a1463a307e9c75d56229da80e71ac78b4eb7a8 -Merge: db5a222 ed6f6f8 +commit 9dde204faa0689aeff2ecf0c687fc1049bd9854e Author: Andrea Maggiulli -Date: Thu Jun 16 18:48:17 2016 +0200 +Date: Fri Dec 23 16:40:55 2016 +0100 + + Issue #103 - This should fix issue. - Finish feature Interpolation + QLNet/Termstructures/Yield/ZeroCurve.cs | 21 +++++--- + Test/T_TermStructures.cs | 90 ++++++++++++++++++++++++++++++++- + 2 files changed, 102 insertions(+), 9 deletions(-) -commit ed6f6f82637c04fd61123c3c96b8a0821a7bdb69 +commit a29e83ce406d363f72f73caad8c8c3d16f8ba609 Author: Andrea Maggiulli -Date: Thu Jun 16 18:47:06 2016 +0200 +Date: Thu Dec 22 17:43:27 2016 +0100 - Added FlatExtrapolator2D + FX feature - Added DoubleBarrier Option, AnalyticDoubleBarrierEngine, VannaVolgaDoubleBarrierEngine, WulinYongDoubleBarrierEngine with tests. - QLNet/Math/Interpolations/FlatExtrapolator2D.cs | 75 +++++++++++++++++++++++++ - QLNet/QLNet.csproj | 1 + - 2 files changed, 76 insertions(+) + QLNet/Instruments/DoubleBarrierOption.cs | 146 ++++++ + .../barrier/AnalyticDoubleBarrierEngine.cs | 237 ++++++++++ + .../barrier/VannaVolgaDoubleBarrierEngine.cs | 356 +++++++++++++++ + .../barrier/WulinYongDoubleBarrierEngine.cs | 172 +++++++ + QLNet/QLNet.csproj | 4 + + Test/T_DoubleBarrierOption.cs | 507 +++++++++++++++++++++ + Test/Test.csproj | 1 + + 7 files changed, 1423 insertions(+) -commit 47b70c0d6f49ef5b0902dd16aae8ebdc79866b3c +commit 8aa30c97d2d62cf6aef0bf05f0552141632673cd Author: Andrea Maggiulli -Date: Thu Jun 16 18:36:58 2016 +0200 +Date: Thu Dec 22 14:18:18 2016 +0100 - Added Mixed Interpolation + FX feature - Added VannaVolga Interpolation and VannaVolga BarrierEngine with test. - QLNet/Math/Interpolation.cs | 4 +- - .../BackwardflatLinearInterpolation.cs | 19 +- - QLNet/Math/Interpolations/MixedInterpolation.cs | 256 +++++++++++++++++++++ - QLNet/QLNet.csproj | 1 + - 4 files changed, 275 insertions(+), 5 deletions(-) + QLNet/Instruments/DividendBarrierOption.cs | 2 +- + .../Math/Interpolations/VannaVolgaInterpolation.cs | 142 ++++++++ + .../barrier/VannaVolgaBarrierEngine.cs | 382 +++++++++++++++++++++ + QLNet/QLNet.csproj | 2 + + Test/T_BarrierOption.cs | 233 ++++++++++++- + 5 files changed, 759 insertions(+), 2 deletions(-) -commit 9f7b57ba95ce9751e070d7dbb692a1e5c3ab40fe +commit 178ddce515e60ca3b5d4ea412490c8eddb506925 Author: Andrea Maggiulli -Date: Thu Jun 16 17:11:27 2016 +0200 +Date: Thu Dec 22 14:15:19 2016 +0100 - Added BackwardflatLinear Interpolation. + FX feature - Added Matrix inverse calculation with Crout's LU decomposition with test. - .../BackwardflatLinearInterpolation.cs | 72 ++++++++++++++++++++++ - QLNet/QLNet.csproj | 1 + - 2 files changed, 73 insertions(+) + QLNet/Math/Matrix.cs | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++- + Test/T_Matrices.cs | 38 ++++++++++++++- + 2 files changed, 169 insertions(+), 4 deletions(-) -commit cbd80ad20df8f5fa2153d83cc6f5c9d59d66cd88 +commit c350b0b183571dc0d915cc1168fc80f1372f33ee Author: Andrea Maggiulli -Date: Thu Jun 16 15:36:36 2016 +0200 +Date: Tue Dec 20 14:38:18 2016 +0100 - Added AbcdInterpolation. + FX feature - Added BlackDeltaCalculator and DeltaVolQuote with tests. - QLNet/Math/Interpolations/Abcdinterpolation.cs | 253 +++++++++++++++++++++++++ - QLNet/QLNet.csproj | 1 + - 2 files changed, 254 insertions(+) + QLNet/Pricingengines/BlackDeltaCalculator.cs | 406 ++++++++++++++++ + QLNet/QLNet.csproj | 2 + + QLNet/Quotes/DeltaVolQuote.cs | 85 ++++ + Test/T_BlackDeltaCalculator.cs | 701 +++++++++++++++++++++++++++ + Test/Test.csproj | 1 + + 5 files changed, 1195 insertions(+) -commit 452d36df228dd9ae0807bb15cb055166be39aefa +commit f3abab53aeb0183fbce470ce5d71eea0c5e23b2d Author: Andrea Maggiulli -Date: Thu Jun 16 15:36:13 2016 +0200 +Date: Wed Dec 14 14:05:49 2016 +0100 - Interpolation update & refactoring. + Added Digital Coupon tests. - QLNet/Math/Interpolation.cs | 299 +++++++++++++++++++++++--------------------- - 1 file changed, 157 insertions(+), 142 deletions(-) + Test/T_DigitalCoupon.cs | 1070 +++++++++++++++++++++++++++++++++++++++++++++++ + Test/Test.csproj | 1 + + 2 files changed, 1071 insertions(+) -commit db5a222e703e7a2cb0f9d19f1c76535fa315130b +commit d1e3f65752a67bd798fb0827752793446bf56245 Author: Andrea Maggiulli -Date: Wed Jun 15 10:55:23 2016 +0200 +Date: Thu Dec 8 15:11:04 2016 +0100 - Added Ukrainian hryvnia & updated Ukraine holidays. + Added Cliquet Option with pricing engines and tests. - QLNet/Currencies/Europe.cs | 11 +++++++++++ - QLNet/Time/Calendars/Ukraine.cs | 27 +++++++++++++++------------ - 2 files changed, 26 insertions(+), 12 deletions(-) + QLNet/Instruments/CliquetOption.cs | 90 +++++++ + .../Cliquet/AnalyticCliquetEngine.cs | 108 ++++++++ + .../Cliquet/AnalyticPerformanceEngine.cs | 105 ++++++++ + QLNet/QLNet.csproj | 3 + + Test/T_CliquetOption.cs | 295 +++++++++++++++++++++ + Test/Test.csproj | 1 + + 6 files changed, 602 insertions(+) -commit 312075a9dd06842d4008f7496e840c639c680e9b -Merge: 7ce1ca0 e73e494 -Author: Andrea Maggiulli -Date: Wed Jun 15 10:12:18 2016 +0200 +commit d32e5fe33fd98ed968b94dfa80e9f7539625159b +Author: Andrea Maggiulli +Date: Wed Dec 7 17:26:20 2016 +0100 - Merge pull request #91 from igitur/correctly-report-irregular-last-period-in-schedule - - Correctly report irregular last period in schedule. + Added CapFloored coupon tests. + + Test/T_CapFlooredCoupon.cs | 509 +++++++++++++++++++++++++++++++++++++++++++++ + Test/Test.csproj | 1 + + 2 files changed, 510 insertions(+) + +commit 9d26c0b8527cdde9542e9fc47e82707210f8e20d +Author: Andrea Maggiulli +Date: Wed Dec 7 17:25:44 2016 +0100 -commit e73e4943b4fbf80f9de610eb248c427d70a094a9 -Author: Francois Botha -Date: Tue Jun 14 18:43:26 2016 +0200 + fix CappedFlooredCoupon factory - Correctly report irregular last period in schedule. + QLNet/Cashflows/CappedFlooredCoupon.cs | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit 71a3ca69d3ded4a347611c71bbfbf9914d5d794a +Author: Andrea Maggiulli +Date: Wed Dec 7 17:24:56 2016 +0100 - QLNet/Time/Schedule.cs | 80 ++++++++++++++++++++++++++------------------------ - Test/T_Schedule.cs | 31 ++++++++++++++++++- - 2 files changed, 71 insertions(+), 40 deletions(-) + optimize npvbps calculation -commit 7ce1ca01df1809cd4bbae3885b92aca99eedc49a -Merge: fc4ec15 698ab47 + QLNet/Cashflows/CashFlows.cs | 18 ++++++++++-------- + 1 file changed, 10 insertions(+), 8 deletions(-) + +commit 847c5b28245ebb4d1c5ea182cfb648efa62b6aa1 Author: Andrea Maggiulli -Date: Tue Jun 14 11:02:34 2016 +0200 +Date: Tue Dec 6 17:22:30 2016 +0100 - Finish Curves Refactoring Feature + Added Test : Chambers-Nawalkha implied vol approximation -commit 13d45829eb24aa07a66171c73a85f933820c27a2 -Author: Stephen Moon -Date: Fri Jun 10 19:11:15 2016 +0100 + Test/T_BlackFormula.cs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 68 insertions(+) - Made some minor changes to grammar and text. +commit 11c95bf79b4e354f2f4a7c43044ed86d80450f08 +Author: Andrea Maggiulli +Date: Tue Dec 6 17:10:17 2016 +0100 - README.md | 31 +++++++++++++++---------------- - 1 file changed, 15 insertions(+), 16 deletions(-) + Added AnalyticBinaryBarrierEngine with tests. -commit 698ab47a981ddffd9e13f8047fb7bfa1fd630f6f + .../barrier/AnalyticBinaryBarrierEngine.cs | 344 +++++++++++++++++++++ + QLNet/QLNet.csproj | 1 + + Test/T_BinaryOption.cs | 283 +++++++++++++++++ + Test/Test.csproj | 1 + + 4 files changed, 629 insertions(+) + +commit ff2d983f24091ba4303a30128f3d6e3ff6f150f0 Author: Andrea Maggiulli -Date: Fri Jun 3 11:59:04 2016 +0200 +Date: Tue Dec 6 15:25:45 2016 +0100 - Fixed TermStructure abstract classes + Added BarrierOption with tests. - QLNet/Termstructures/Credit/HazardRateStructure.cs | 7 +++---- - .../DefaultProbabilityTermStructure.cs | 9 ++++----- - QLNet/Termstructures/InflationTermStructure.cs | 23 +++++++++++----------- - QLNet/Termstructures/TermStructure.cs | 10 +++++----- - .../Bond/CallableBondVolatilityStructure.cs | 16 +++++++-------- - .../CapFloor/CapFloorTermVolatilityStructure.cs | 8 ++++---- - .../Volatility/Inflation/CPIVolatilitySurface.cs | 10 +++++----- - .../yoyinflationoptionletvolatilitystructure.cs | 13 +++--------- - .../Optionlet/OptionletVolatilityStructure.cs | 9 +++++---- - .../Volatility/equityfx/BlackVolTermStructure.cs | 15 +++++++------- - .../Volatility/equityfx/LocalVolTermStructure.cs | 7 ++++--- - .../swaption/SwaptionVolatilityStructure.cs | 9 +++++---- - .../Volatility/swaption/swaptionvoldiscrete.cs | 3 ++- - QLNet/Termstructures/Yield/Zeroyieldstructure.cs | 5 +++-- - QLNet/Termstructures/YieldTermStructure.cs | 6 +++--- - QLNet/Termstructures/voltermstructure.cs | 7 ++++--- - Test/T_Inflation.cs | 6 +++--- - Test/T_InflationCapFloorTest.cs | 8 ++++---- - Test/T_InflationCapFlooredCouponTest.cs | 6 +++--- - 19 files changed, 86 insertions(+), 91 deletions(-) + .../barrier/BinomialBarrierEngine.cs | 182 +++++ + .../barrier/DiscretizedBarrierOption.cs | 243 +++++++ + QLNet/QLNet.csproj | 2 + + Test/T_BarrierOption.cs | 785 +++++++++++++++++++++ + Test/Test.csproj | 1 + + 5 files changed, 1213 insertions(+) -commit fc4ec15264868203c1004421ad55866c077fabf3 -Merge: 09c52b6 2c49bce +commit 26465eb87736cf9bb15b352602e3d63f15460e50 Author: Andrea Maggiulli -Date: Thu Jun 2 09:43:39 2016 +0200 +Date: Tue Dec 6 13:01:59 2016 +0100 + + Refactoring & Update BlackScholesProcess, LocalVolCurve, bsmlattice - Finish index feature + QLNet/Methods/lattices/bsmlattice.cs | 123 +++-- + .../Volatility/equityfx/LocalVolCurve.cs | 112 +++-- + QLNet/processes/BlackScholesProcess.cs | 520 ++++++++++++--------- + 3 files changed, 462 insertions(+), 293 deletions(-) -commit 2c49bce65d523306b6b542d6fdb84f1ef3399152 +commit d364bd11df894762256e28d6f0291b2532dd754b Author: Andrea Maggiulli -Date: Wed Jun 1 13:56:57 2016 +0200 +Date: Wed Nov 30 12:01:02 2016 +0100 - Fix project configuration. + Added tests for China SSE and IB calendars and a missing Chinese holiday - QLNet/Cashflows/RangeAccrual.cs | 667 ++++++++++++++++++++++++++++++++++++++++ - QLNet/QLNet.csproj | 1 - - 2 files changed, 667 insertions(+), 1 deletion(-) + QLNet/Time/Calendars/china.cs | 15 +++- + Test/T_Calendars.cs | 169 +++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 180 insertions(+), 4 deletions(-) -commit ed53c11c20b28875fdf5e8d54bcbf79ea42ccd4a +commit b8039da3cd702b108fdc1c078d94697f829719ae Author: Andrea Maggiulli -Date: Wed Jun 1 13:54:15 2016 +0200 +Date: Wed Nov 30 11:58:44 2016 +0100 - Added CounterpartyAdjSwapEngine engine with example program. + Fixed United States holidays before 1971 - Examples/CVAIRS/CVAIRS.cs | 189 +++++++++++++++++ - Examples/CVAIRS/CVAIRS.csproj | 61 ++++++ - Examples/CVAIRS/Properties/AssemblyInfo.cs | 36 ++++ - QLNet/Instruments/VanillaSwap.cs | 4 +- - .../Swap/CounterpartyAdjSwapEngine.cs | 232 +++++++++++++++++++++ - QLNet/Pricingengines/Swap/Discountingswapengine.cs | 34 +-- - QLNet/QLNet.csproj | 2 + - QLNet_with_Examples.sln | 12 ++ - 8 files changed, 556 insertions(+), 14 deletions(-) - -commit a16b9cf81e96db6dd7c36c1f0ca0a52ae047f466 -Author: Andrea Maggiulli -Date: Wed Jun 1 13:53:35 2016 +0200 - - Updated isExpired() methods with Event interface. - - QLNet/Event.cs | 17 ++++++++++++++++- - QLNet/Instruments/Instrument.cs | 2 +- - QLNet/Instruments/MultiAssetOption.cs | 2 +- - QLNet/Instruments/OneAssetOption.cs | 2 +- - QLNet/Instruments/Swaption.cs | 2 +- - QLNet/Instruments/forward.cs | 6 +----- - QLNet/Instruments/forwardrateagreement.cs | 6 +----- - 7 files changed, 22 insertions(+), 15 deletions(-) - -commit 09c52b6534c4c67d93fe2e28a10b9f2b63a6d3b2 -Author: Francois Botha -Date: Mon May 30 17:40:38 2016 +0200 - - Implemented Lagrange boundary condition for cubic interpolation - - QLNet/Math/Interpolations/CubicInterpolation.cs | 81 ++++++++++++++++++------- - 1 file changed, 59 insertions(+), 22 deletions(-) - -commit 437c40be02c4ed4b21619737da89f20258909d90 -Author: Andrea Maggiulli -Date: Mon May 30 11:36:26 2016 +0200 - - CashFlows refactoring & update. - Part 1 - - QLNet/Cashflows/CPICoupon.cs | 5 +- - QLNet/Cashflows/CappedFlooredCoupon.cs | 18 +- - QLNet/Cashflows/CappedFlooredYoYInflationCoupon.cs | 4 +- - QLNet/Cashflows/CashFlows.cs | 20 +- - QLNet/Cashflows/Cashflowvectors.cs | 15 +- - QLNet/Cashflows/CmsCoupon.cs | 5 +- - QLNet/Cashflows/ConundrumPricer.cs | 6 +- - QLNet/Cashflows/Coupon.cs | 112 ++-- - QLNet/Cashflows/CouponPricer.cs | 720 ++++++++++++--------- - QLNet/Cashflows/DigitalCoupon.cs | 2 +- - QLNet/Cashflows/FixedRateCoupon.cs | 26 +- - QLNet/Cashflows/FloatingRateCoupon.cs | 14 +- - QLNet/Cashflows/Iborcoupon.cs | 140 ++-- - QLNet/Cashflows/InflationCoupon.cs | 4 +- - QLNet/Cashflows/OvernightIndexedCoupon.cs | 7 +- - QLNet/Cashflows/averagebmacoupon.cs | 462 ++++++------- - QLNet/Indexes/IBORIndex.cs | 2 +- - QLNet/Instruments/Bonds/AmortizingBond.cs | 22 +- - QLNet/Instruments/Bonds/MBSFixedRateBond.cs | 2 +- - QLNet/Instruments/Loan.cs | 2 +- - QLNet/QLNet.csproj | 3 +- - .../Optionlet/OptionletVolatilityStructure.cs | 4 + - Test/T_CashFlows.cs | 2 +- - 23 files changed, 876 insertions(+), 721 deletions(-) - -commit c6c116ca526b67b8cf56241092a9e1101c7c8079 -Author: Andrea Maggiulli -Date: Thu May 26 18:18:14 2016 +0200 - - Indexes refactoring & update - - QLNet/Index.cs | 206 +++++---- - QLNet/Indexes/IBORIndex.cs | 188 ++++---- - QLNet/Indexes/InflationIndex.cs | 849 ++++++++++++++++++------------------- - QLNet/Indexes/InterestRateIndex.cs | 262 ++++++------ - QLNet/Indexes/Swapindex.cs | 424 +++++++++--------- - QLNet/Indexes/bmaindex.cs | 169 ++++---- - QLNet/QLNet.csproj | 4 +- - 7 files changed, 1085 insertions(+), 1017 deletions(-) - -commit fed563b23f78d6893066cb1a795c670f82929d1e -Author: Francois Botha -Date: Wed May 25 17:52:12 2016 +0200 - - Fix tests to Asserts instead of Console.WriteLine statements (#87) - - Test/T_Piecewiseyieldcurve.cs | 71 ++++++++++++++++++++----------------------- - 1 file changed, 33 insertions(+), 38 deletions(-) - -commit 9529484b3086c0af95e76926526c2baf997e4212 -Author: Francois Botha -Date: Wed May 25 17:39:08 2016 +0200 - - previousData should be initialized as a NEW List, else it is just a reference to data_ List and will change when data_ changes. (#86) - - QLNet/Termstructures/Iterativebootstrap.cs | 2 +- + QLNet/Time/Calendars/UnitedStates.cs | 487 ++++++++++++++++++++--------------- + 1 file changed, 273 insertions(+), 214 deletions(-) + +commit 9830843dab74c1b34c52bc9434c2249587e18b0e +Author: Andrea Maggiulli +Date: Wed Nov 30 11:58:09 2016 +0100 + + Fixed rule for the Japanese Mountain Day holiday + + QLNet/Time/Calendars/japan.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) -commit c1342e901dbb106aa58dcf75659e3e7e274c764c +commit d90c3ebd3b0233e3ae520e0d0f686e9115bb4228 Author: Andrea Maggiulli -Date: Tue May 24 15:24:17 2016 +0200 +Date: Wed Nov 30 11:57:03 2016 +0100 - Fixed links to documentation for LIBOR indexes & reformat code. + Added ECB dates for 2017 - QLNet/Indexes/Ibor/Audlibor.cs | 41 ++- - QLNet/Indexes/Ibor/Cadlibor.cs | 72 +++-- - QLNet/Indexes/Ibor/Chflibor.cs | 54 ++-- - QLNet/Indexes/Ibor/Dkklibor.cs | 40 ++- - QLNet/Indexes/Ibor/Eurlibor.cs | 587 ++++++++++++++++++++++------------------- - QLNet/Indexes/Ibor/Gbplibor.cs | 66 +++-- - QLNet/Indexes/Ibor/Jpylibor.cs | 58 ++-- - QLNet/Indexes/Ibor/Libor.cs | 251 ++++++++++-------- - QLNet/Indexes/Ibor/Nzdlibor.cs | 41 ++- - QLNet/Indexes/Ibor/Seklibor.cs | 40 +-- - QLNet/Indexes/Ibor/Trylibor.cs | 43 +-- - QLNet/Indexes/Ibor/Usdlibor.cs | 65 +++-- - 12 files changed, 728 insertions(+), 630 deletions(-) + QLNet/Time/ECB.cs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) -commit e3a58fde9160783faafca75f1953283c1bb7d2b9 +commit 881aa9e36e7fc9b51a69ef837fb7f1fb1b2c3dec Author: Andrea Maggiulli -Date: Tue May 24 10:24:56 2016 +0200 +Date: Wed Nov 30 11:43:24 2016 +0100 + + Allow negative jumps in yield term structures. + + QLNet/Termstructures/YieldTermStructure.cs | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +commit 48cb25fde280719f672cd0bedae4b2423408706c +Merge: 5c0eab7 07a5010 +Author: Andrea Maggiulli +Date: Tue Nov 29 10:40:46 2016 +0100 + + Merge pull request #111 from callmecary/feature/lianze + + made Bicubic inherited from IInterpolationFactory2D, so that vol surface like BlackVarianceSurface can set it to interpolation + +commit 07a5010d93ce29b87b0898730a35a9b329c9c1db +Author: Ma, Lianze +Date: Mon Nov 28 14:34:43 2016 -0500 - Fix tests , if something wrong a test have to fail. + made Bicubic inherited from IInterpolationFactory2D, so that vol surface like BlackVarianceSurface can set it to interpolation - Test/T_Bonds.cs | 12 ++++++------ - Test/T_Swaps.cs | 1 - - 2 files changed, 6 insertions(+), 7 deletions(-) + .../Interpolations/BicubicSplineInterpolation.cs | 80 +++++++++++----------- + 1 file changed, 40 insertions(+), 40 deletions(-) -commit 28948c78dc98a099b64cd330885f4ee3c5ccba2f +commit 5c0eab7d52e7a6b088abcfd920233fe355f95dbb Author: Andrea Maggiulli -Date: Tue May 24 10:15:10 2016 +0200 +Date: Mon Nov 28 14:52:44 2016 +0100 - Fix tests , if something wrong a test have to fail. + Revert "Make theta pertubation in AmericanOption & DividendOption tests." + + This reverts commit 3bbf8f8049b468e20a107d61ed0336b1a202d967. - Test/T_TermStructures.cs | 22 +++++++++++----------- - 1 file changed, 11 insertions(+), 11 deletions(-) + Test/T_AmericanOption.cs | 12 ++++++------ + Test/T_DividendOption.cs | 18 +++++++++--------- + 2 files changed, 15 insertions(+), 15 deletions(-) -commit c9306af2ffab1dd546090c4c66acd9fbacbb132f +commit 3bbf8f8049b468e20a107d61ed0336b1a202d967 Author: Andrea Maggiulli -Date: Mon May 23 14:07:49 2016 +0200 +Date: Mon Nov 28 14:02:53 2016 +0100 - Disabled failing test. + Make theta pertubation in AmericanOption & DividendOption tests. - Test/T_Piecewiseyieldcurve.cs | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + Test/T_AmericanOption.cs | 12 ++++++------ + Test/T_DividendOption.cs | 18 +++++++++--------- + 2 files changed, 15 insertions(+), 15 deletions(-) + +commit 0b972139989d3c9ba0ba3c9ebabb9c768e3103b3 +Author: Andrea Maggiulli +Date: Wed Nov 16 11:58:10 2016 +0100 + + Added new Ibor indexes : Aonia , Bbsw, Bkbm and Nzocr. + + QLNet/Indexes/Ibor/Aonia.cs | 31 ++++++++++++++++ + QLNet/Indexes/Ibor/Bbsw.cs | 85 +++++++++++++++++++++++++++++++++++++++++++ + QLNet/Indexes/Ibor/Bkbm.cs | 88 +++++++++++++++++++++++++++++++++++++++++++++ + QLNet/Indexes/Ibor/Nzocr.cs | 32 +++++++++++++++++ + QLNet/QLNet.csproj | 4 +++ + 5 files changed, 240 insertions(+) + +commit 7de335002ee933c05720db9e6b7a884c1061082f +Author: Andrea Maggiulli +Date: Wed Nov 16 10:15:46 2016 +0100 + + CMS Helper and tests + + QLNet/Cashflows/ConundrumPricer.cs | 27 +- + QLNet/Cashflows/LinearTsrPricer.cs | 556 +++++++++++++++++++++ + QLNet/Instruments/MakeCms.cs | 348 +++++++++++++ + QLNet/QLNet.csproj | 3 + + QLNet/Termstructures/Volatility/AtmSmileSection.cs | 45 ++ + .../Termstructures/Volatility/FlatSmileSection.cs | 2 +- + Test/T_Cms.cs | 467 +++++++++++++++++ + Test/Test.csproj | 3 + + 8 files changed, 1442 insertions(+), 9 deletions(-) + +commit a21bb52afc3b123659da5a2b30aae55dbe319a71 +Author: Andrea Maggiulli +Date: Mon Nov 14 14:14:52 2016 +0100 + + Added Swaption volatility cube with tests. + + QLNet/QLNet.csproj | 4 + + .../Volatility/InterpolatedSmileSection.cs | 123 +-- + QLNet/Termstructures/Volatility/Sabr.cs | 6 +- + .../swaption/SpreadedSwaptionVolatility.cs | 82 ++ + .../Volatility/swaption/SwaptionVolCube1.cs | 980 +++++++++++++++++++++ + .../Volatility/swaption/SwaptionVolCube2.cs | 115 +++ + .../Volatility/swaption/SwaptionVolatilityCube.cs | 218 +++++ + .../swaption/SwaptionVolatilityStructure.cs | 60 ++ + .../Volatility/swaption/swaptionvoldiscrete.cs | 8 +- + QLNet/Time/Period.cs | 13 +- + README.md | 1 + + Test/T_SwaptionVolatilityCube.cs | 424 +++++++++ + Test/Test.csproj | 1 + + Test/Utilities.cs | 269 ++++-- + 14 files changed, 2163 insertions(+), 141 deletions(-) + +commit 0d37226a4445ddf78aacd922eec94410c930b50b +Author: Andrea Maggiulli +Date: Wed Oct 12 12:47:24 2016 +0200 + + Instruments feature : add LookbackOption with engines and tests. + + QLNet/Instruments/LookbackOption.cs | 223 +++++++++ + .../AnalyticContinuousFixedLookbackEngine.cs | 125 ++++++ + .../AnalyticContinuousFloatingLookbackEngine.cs | 89 ++++ + ...AnalyticContinuousPartialFixedLookbackEngine.cs | 134 ++++++ + ...lyticContinuousPartialFloatingLookbackEngine.cs | 155 +++++++ + QLNet/QLNet.csproj | 5 + + Test/T_LookbackOption.cs | 497 +++++++++++++++++++++ + Test/Test.csproj | 1 + + 8 files changed, 1229 insertions(+) + +commit afa34ae97e5a5911567ffbb75a305dc368511a21 +Author: Andrea Maggiulli +Date: Tue Oct 11 12:42:42 2016 +0200 + + Instruments feature : add CompositeInstrument,DividendBarrierOption,ForwardVanillaOption with engine and tests. -commit 3eac6d3949e0e3cb2aa1ee531847385f0f18dd86 -Author: Andrea Maggiulli -Date: Mon May 23 13:58:24 2016 +0200 - - Fixed null values in Pricing Engines, thx @tomislav-t for spotting it. - - QLNet/Pricingengines/mclongstaffschwartzengine.cs | 368 +++++++------- - QLNet/Pricingengines/mcsimulation.cs | 46 +- - .../vanilla/MCEuropeanHestonEngine.cs | 10 +- - .../vanilla/MCHestonHullWhiteEngine.cs | 12 +- - QLNet/Pricingengines/vanilla/mcamericanengine.cs | 533 +++++++++++---------- - QLNet/Pricingengines/vanilla/mceuropeanengine.cs | 312 ++++++------ - QLNet/Pricingengines/vanilla/mcvanillaengine.cs | 35 +- - 7 files changed, 709 insertions(+), 607 deletions(-) - -commit 51ac0d337d6b145da36703afa35bd374e90958d1 -Author: Andrea Maggiulli -Date: Fri May 20 16:36:47 2016 +0200 - - Fixed all tests Dispose and making IndexHistoryCleaner disposable too. - - Test/T_AmericanOption.cs | 230 +++++++++++++++++--------------- - Test/T_AssetSwap.cs | 25 +++- - Test/T_Bermudanswaption.cs | 19 ++- - Test/T_Bonds.cs | 151 +++++++++++---------- - Test/T_CapFloor.cs | 18 ++- - Test/T_EuropeanOption.cs | 59 +++----- - Test/T_HybridHestonHullWhiteProcess.cs | 32 ++--- - Test/T_InflationCapFloorTest.cs | 17 ++- - Test/T_InflationCapFlooredCouponTest.cs | 18 ++- - Test/T_LiborMarketModel.cs | 35 ++--- - Test/T_LiborMarketModelProcess.cs | 36 +++-- - Test/T_LinearLeastSquaresRegression.cs | 42 +++--- - Test/T_OptionletStripper.cs | 23 ++-- - Test/T_OvernightIndexedSwap.cs | 20 ++- - Test/T_PathGenerator.cs | 31 +++-- - Test/T_Piecewiseyieldcurve.cs | 31 +++-- - Test/T_Swaps.cs | 46 +++---- - Test/T_Swaption.cs | 47 +++---- - Test/T_SwaptionVolatilitymatrix.cs | 34 ++--- - Test/T_TermStructures.cs | 50 +++---- - Test/Utilities.cs | 7 +- - 21 files changed, 525 insertions(+), 446 deletions(-) + .gitignore | 1 + + QLNet/Instruments/CompositeInstrument.cs | 72 +++ + QLNet/Instruments/DividendBarrierOption.cs | 75 +++ + QLNet/Instruments/ForwardVanillaOption.cs | 73 +++ + .../Forward/ForwardPerformanceVanillaEngine.cs | 64 +++ + .../Pricingengines/Forward/ForwardVanillaEngine.cs | 128 +++++ + QLNet/QLNet.csproj | 6 + + .../Volatility/equityfx/ImpliedVolTermStructure.cs | 74 +++ + Test/T_ForwardOption.cs | 538 +++++++++++++++++++++ + Test/Test.csproj | 1 + + 10 files changed, 1032 insertions(+) \ No newline at end of file diff --git a/Examples/BermudanSwaption/BermudanSwaption.cs b/Examples/BermudanSwaption/BermudanSwaption.cs index 515f6ded0..fc27e9998 100644 --- a/Examples/BermudanSwaption/BermudanSwaption.cs +++ b/Examples/BermudanSwaption/BermudanSwaption.cs @@ -1,297 +1,297 @@ -/* - Copyright (C) 2010 Philippe Real (ph_real@hotmail.com) - - This file is part of QLNet Project http://qlnet.sourceforge.net/ - - 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; -using QLNet; - -namespace BermudanSwaption -{ - class BermudanSwaption - { - - //Number of swaptions to be calibrated to... - const int NumRows = 5; - const int NumCols = 5; - - static readonly int[] SwapLenghts = { 1, 2, 3, 4, 5 }; - static readonly double[] SwaptionVols = { - 0.1490, 0.1340, 0.1228, 0.1189, 0.1148, - 0.1290, 0.1201, 0.1146, 0.1108, 0.1040, - 0.1149, 0.1112, 0.1070, 0.1010, 0.0957, - 0.1047, 0.1021, 0.0980, 0.0951, 0.1270, - 0.1000, 0.0950, 0.0900, 0.1230, 0.1160}; - - static void CalibrateModel(ShortRateModel model, - List helpers) - { - if (model == null) throw new ArgumentNullException("model"); - var om = new LevenbergMarquardt(); - model.calibrate(helpers, om, - new EndCriteria(400, 100, 1.0e-8, 1.0e-8, 1.0e-8), new Constraint(), - new List()); - - // Output the implied Black volatilities - for (int i = 0; i < NumRows; i++) - { - int j = NumCols - i - 1; // 1x5, 2x4, 3x3, 4x2, 5x1 - int k = i * NumCols + j; - double npv = helpers[i].modelValue(); - double implied = helpers[i].impliedVolatility(npv, 1e-4, - 1000, 0.05, 0.50); - double diff = implied - SwaptionVols[k]; - Console.WriteLine("{0}x{1}: model {2:0.00000 %}, market {3:0.00000 %}, diff {4:0.00000 %} ", - i + 1, SwapLenghts[j], implied, SwaptionVols[k], diff); - } - } - - static void Main(string[] args) - { - - DateTime timer = DateTime.Now; - - Date todaysDate = new Date(15, 2, 2002); - Calendar calendar = new TARGET(); - Date settlementDate = new Date(19, 2, 2002); - Settings.setEvaluationDate(todaysDate); - - // flat yield term structure impling 1x5 swap at 5% - Quote flatRate = new SimpleQuote(0.04875825); - Handle rhTermStructure = new Handle( - new FlatForward(settlementDate, new Handle(flatRate), - new Actual365Fixed())); - - // Define the ATM/OTM/ITM swaps - Frequency fixedLegFrequency = Frequency.Annual; - BusinessDayConvention fixedLegConvention = BusinessDayConvention.Unadjusted; - BusinessDayConvention floatingLegConvention = BusinessDayConvention.ModifiedFollowing; - DayCounter fixedLegDayCounter = new Thirty360(Thirty360.Thirty360Convention.European); - Frequency floatingLegFrequency = Frequency.Semiannual; - VanillaSwap.Type type = VanillaSwap.Type.Payer; - double dummyFixedRate = 0.03; - IborIndex indexSixMonths = new Euribor6M(rhTermStructure); - - Date startDate = calendar.advance(settlementDate, 1, TimeUnit.Years, - floatingLegConvention); - Date maturity = calendar.advance(startDate, 5, TimeUnit.Years, - floatingLegConvention); - Schedule fixedSchedule = new Schedule(startDate, maturity, new Period(fixedLegFrequency), - calendar, fixedLegConvention, fixedLegConvention, - DateGeneration.Rule.Forward, false); - Schedule floatSchedule = new Schedule(startDate, maturity, new Period(floatingLegFrequency), - calendar, floatingLegConvention, floatingLegConvention, - DateGeneration.Rule.Forward, false); - - VanillaSwap swap = new VanillaSwap( - type, 1000.0, - fixedSchedule, dummyFixedRate, fixedLegDayCounter, - floatSchedule, indexSixMonths, 0.0, - indexSixMonths.dayCounter()); - swap.setPricingEngine(new DiscountingSwapEngine(rhTermStructure)); - double fixedAtmRate = swap.fairRate(); - double fixedOtmRate = fixedAtmRate * 1.2; - double fixedItmRate = fixedAtmRate * 0.8; - - VanillaSwap atmSwap = new VanillaSwap( - type, 1000.0, - fixedSchedule, fixedAtmRate, fixedLegDayCounter, - floatSchedule, indexSixMonths, 0.0, - indexSixMonths.dayCounter()); - VanillaSwap otmSwap = new VanillaSwap( - type, 1000.0, - fixedSchedule, fixedOtmRate, fixedLegDayCounter, - floatSchedule, indexSixMonths, 0.0, - indexSixMonths.dayCounter()); - VanillaSwap itmSwap = new VanillaSwap( - type, 1000.0, - fixedSchedule, fixedItmRate, fixedLegDayCounter, - floatSchedule, indexSixMonths, 0.0, - indexSixMonths.dayCounter()); - - // defining the swaptions to be used in model calibration - List swaptionMaturities = new List(5); - swaptionMaturities.Add(new Period(1, TimeUnit.Years)); - swaptionMaturities.Add(new Period(2, TimeUnit.Years)); - swaptionMaturities.Add(new Period(3, TimeUnit.Years)); - swaptionMaturities.Add(new Period(4, TimeUnit.Years)); - swaptionMaturities.Add(new Period(5, TimeUnit.Years)); - - List swaptions = new List(); - - // List of times that have to be included in the timegrid - List times = new List(); - - for (int i = 0; i < NumRows; i++) - { - int j = NumCols - i - 1; // 1x5, 2x4, 3x3, 4x2, 5x1 - int k = i * NumCols + j; - Quote vol = new SimpleQuote(SwaptionVols[k]); - swaptions.Add(new SwaptionHelper(swaptionMaturities[i], - new Period(SwapLenghts[j], TimeUnit.Years), - new Handle(vol), - indexSixMonths, - indexSixMonths.tenor(), - indexSixMonths.dayCounter(), - indexSixMonths.dayCounter(), - rhTermStructure)); - swaptions.Last().addTimesTo(times); - } - - // Building time-grid - TimeGrid grid = new TimeGrid(times, 30); - - - // defining the models - G2 modelG2 = new G2(rhTermStructure); - HullWhite modelHw = new HullWhite(rhTermStructure); - HullWhite modelHw2 = new HullWhite(rhTermStructure); - BlackKarasinski modelBk = new BlackKarasinski(rhTermStructure); - - - // model calibrations - - Console.WriteLine("G2 (analytic formulae) calibration"); - for (int i = 0; i < swaptions.Count; i++) - swaptions[i].setPricingEngine(new G2SwaptionEngine(modelG2, 6.0, 16)); - CalibrateModel(modelG2, swaptions); - Console.WriteLine("calibrated to:\n" + - "a = {0:0.000000}, " + - "sigma = {1:0.0000000}\n" + - "b = {2:0.000000}, " + - "eta = {3:0.0000000}\n" + - "rho = {4:0.00000}\n", - modelG2.parameters()[0], - modelG2.parameters()[1], - modelG2.parameters()[2], - modelG2.parameters()[3], - modelG2.parameters()[4]); - - Console.WriteLine("Hull-White (analytic formulae) calibration"); - for (int i = 0; i < swaptions.Count; i++) - swaptions[i].setPricingEngine(new JamshidianSwaptionEngine(modelHw)); - CalibrateModel(modelHw, swaptions); - Console.WriteLine("calibrated to:\n" + - "a = {0:0.000000}, " + - "sigma = {1:0.0000000}\n", - modelHw.parameters()[0], - modelHw.parameters()[1]); - - Console.WriteLine("Hull-White (numerical) calibration"); - for (int i = 0; i < swaptions.Count(); i++) - swaptions[i].setPricingEngine(new TreeSwaptionEngine(modelHw2, grid)); - CalibrateModel(modelHw2, swaptions); - Console.WriteLine("calibrated to:\n" + - "a = {0:0.000000}, " + - "sigma = {1:0.0000000}\n", - modelHw2.parameters()[0], - modelHw2.parameters()[1]); - - Console.WriteLine("Black-Karasinski (numerical) calibration"); - for (int i = 0; i < swaptions.Count; i++) - swaptions[i].setPricingEngine(new TreeSwaptionEngine(modelBk, grid)); - CalibrateModel(modelBk, swaptions); - Console.WriteLine("calibrated to:\n" + - "a = {0:0.000000}, " + - "sigma = {1:0.00000}\n", - modelBk.parameters()[0], - modelBk.parameters()[1]); - - - // ATM Bermudan swaption pricing - Console.WriteLine("Payer bermudan swaption " - + "struck at {0:0.00000 %} (ATM)", - fixedAtmRate); - - List bermudanDates = new List(); - List leg = swap.fixedLeg(); - for (int i = 0; i < leg.Count; i++) - { - Coupon coupon = (Coupon)leg[i]; - bermudanDates.Add(coupon.accrualStartDate()); - } - - Exercise bermudanExercise = new BermudanExercise(bermudanDates); - - Swaption bermudanSwaption = new Swaption(atmSwap, bermudanExercise); - - // Do the pricing for each model - - // G2 price the European swaption here, it should switch to bermudan - bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelG2, 50)); - Console.WriteLine("G2: {0:0.00}", bermudanSwaption.NPV()); - - bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw, 50)); - Console.WriteLine("HW: {0:0.000}", bermudanSwaption.NPV()); - - bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw2, 50)); - Console.WriteLine("HW (num): {0:0.000}", bermudanSwaption.NPV()); - - bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelBk, 50)); - Console.WriteLine("BK: {0:0.000}", bermudanSwaption.NPV()); - - - // OTM Bermudan swaption pricing - Console.WriteLine("Payer bermudan swaption " - + "struck at {0:0.00000 %} (OTM)", - fixedOtmRate); - - Swaption otmBermudanSwaption = new Swaption(otmSwap, bermudanExercise); - - // Do the pricing for each model - otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelG2, 50)); - Console.WriteLine("G2: {0:0.0000}", otmBermudanSwaption.NPV()); - - otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw, 50)); - Console.WriteLine("HW: {0:0.0000}", otmBermudanSwaption.NPV()); - - otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw2, 50)); - Console.WriteLine("HW (num): {0:0.000}", otmBermudanSwaption.NPV()); - - otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelBk, 50)); - Console.WriteLine("BK: {0:0.0000}", otmBermudanSwaption.NPV()); - - // ITM Bermudan swaption pricing - Console.WriteLine("Payer bermudan swaption " - + "struck at {0:0.00000 %} (ITM)", - fixedItmRate); - - Swaption itmBermudanSwaption = new Swaption(itmSwap, bermudanExercise); - - // Do the pricing for each model - itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelG2, 50)); - Console.WriteLine("G2: {0:0.000}", itmBermudanSwaption.NPV()); - - itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw, 50)); - Console.WriteLine("HW: {0:0.000}", itmBermudanSwaption.NPV()); - - itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw2, 50)); - Console.WriteLine("HW (num): {0:0.000}", itmBermudanSwaption.NPV()); - - itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelBk, 50)); - Console.WriteLine("BK: {0:0.000}", itmBermudanSwaption.NPV()); - - - Console.WriteLine(" \nRun completed in {0}", DateTime.Now - timer); - Console.WriteLine(); - - Console.Write("Press any key to continue ..."); - Console.ReadKey(); - } - } -} \ No newline at end of file +/* + Copyright (C) 2010 Philippe Real (ph_real@hotmail.com) + + This file is part of QLNet Project http://qlnet.sourceforge.net/ + + 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; +using QLNet; + +namespace BermudanSwaption +{ + class BermudanSwaption + { + + //Number of swaptions to be calibrated to... + const int NumRows = 5; + const int NumCols = 5; + + static readonly int[] SwapLenghts = { 1, 2, 3, 4, 5 }; + static readonly double[] SwaptionVols = { + 0.1490, 0.1340, 0.1228, 0.1189, 0.1148, + 0.1290, 0.1201, 0.1146, 0.1108, 0.1040, + 0.1149, 0.1112, 0.1070, 0.1010, 0.0957, + 0.1047, 0.1021, 0.0980, 0.0951, 0.1270, + 0.1000, 0.0950, 0.0900, 0.1230, 0.1160}; + + static void CalibrateModel(ShortRateModel model, + List helpers) + { + if (model == null) throw new ArgumentNullException("model"); + var om = new LevenbergMarquardt(); + model.calibrate(helpers, om, + new EndCriteria(400, 100, 1.0e-8, 1.0e-8, 1.0e-8), new Constraint(), + new List()); + + // Output the implied Black volatilities + for (int i = 0; i < NumRows; i++) + { + int j = NumCols - i - 1; // 1x5, 2x4, 3x3, 4x2, 5x1 + int k = i * NumCols + j; + double npv = helpers[i].modelValue(); + double implied = helpers[i].impliedVolatility(npv, 1e-4, + 1000, 0.05, 0.50); + double diff = implied - SwaptionVols[k]; + Console.WriteLine("{0}x{1}: model {2:0.00000 %}, market {3:0.00000 %}, diff {4:0.00000 %} ", + i + 1, SwapLenghts[j], implied, SwaptionVols[k], diff); + } + } + + static void Main(string[] args) + { + + DateTime timer = DateTime.Now; + + Date todaysDate = new Date(15, 2, 2002); + Calendar calendar = new TARGET(); + Date settlementDate = new Date(19, 2, 2002); + Settings.setEvaluationDate(todaysDate); + + // flat yield term structure impling 1x5 swap at 5% + Quote flatRate = new SimpleQuote(0.04875825); + Handle rhTermStructure = new Handle( + new FlatForward(settlementDate, new Handle(flatRate), + new Actual365Fixed())); + + // Define the ATM/OTM/ITM swaps + Frequency fixedLegFrequency = Frequency.Annual; + BusinessDayConvention fixedLegConvention = BusinessDayConvention.Unadjusted; + BusinessDayConvention floatingLegConvention = BusinessDayConvention.ModifiedFollowing; + DayCounter fixedLegDayCounter = new Thirty360(Thirty360.Thirty360Convention.European); + Frequency floatingLegFrequency = Frequency.Semiannual; + VanillaSwap.Type type = VanillaSwap.Type.Payer; + double dummyFixedRate = 0.03; + IborIndex indexSixMonths = new Euribor6M(rhTermStructure); + + Date startDate = calendar.advance(settlementDate, 1, TimeUnit.Years, + floatingLegConvention); + Date maturity = calendar.advance(startDate, 5, TimeUnit.Years, + floatingLegConvention); + Schedule fixedSchedule = new Schedule(startDate, maturity, new Period(fixedLegFrequency), + calendar, fixedLegConvention, fixedLegConvention, + DateGeneration.Rule.Forward, false); + Schedule floatSchedule = new Schedule(startDate, maturity, new Period(floatingLegFrequency), + calendar, floatingLegConvention, floatingLegConvention, + DateGeneration.Rule.Forward, false); + + VanillaSwap swap = new VanillaSwap( + type, 1000.0, + fixedSchedule, dummyFixedRate, fixedLegDayCounter, + floatSchedule, indexSixMonths, 0.0, + indexSixMonths.dayCounter()); + swap.setPricingEngine(new DiscountingSwapEngine(rhTermStructure)); + double fixedAtmRate = swap.fairRate(); + double fixedOtmRate = fixedAtmRate * 1.2; + double fixedItmRate = fixedAtmRate * 0.8; + + VanillaSwap atmSwap = new VanillaSwap( + type, 1000.0, + fixedSchedule, fixedAtmRate, fixedLegDayCounter, + floatSchedule, indexSixMonths, 0.0, + indexSixMonths.dayCounter()); + VanillaSwap otmSwap = new VanillaSwap( + type, 1000.0, + fixedSchedule, fixedOtmRate, fixedLegDayCounter, + floatSchedule, indexSixMonths, 0.0, + indexSixMonths.dayCounter()); + VanillaSwap itmSwap = new VanillaSwap( + type, 1000.0, + fixedSchedule, fixedItmRate, fixedLegDayCounter, + floatSchedule, indexSixMonths, 0.0, + indexSixMonths.dayCounter()); + + // defining the swaptions to be used in model calibration + List swaptionMaturities = new List(5); + swaptionMaturities.Add(new Period(1, TimeUnit.Years)); + swaptionMaturities.Add(new Period(2, TimeUnit.Years)); + swaptionMaturities.Add(new Period(3, TimeUnit.Years)); + swaptionMaturities.Add(new Period(4, TimeUnit.Years)); + swaptionMaturities.Add(new Period(5, TimeUnit.Years)); + + List swaptions = new List(); + + // List of times that have to be included in the timegrid + List times = new List(); + + for (int i = 0; i < NumRows; i++) + { + int j = NumCols - i - 1; // 1x5, 2x4, 3x3, 4x2, 5x1 + int k = i * NumCols + j; + Quote vol = new SimpleQuote(SwaptionVols[k]); + swaptions.Add(new SwaptionHelper(swaptionMaturities[i], + new Period(SwapLenghts[j], TimeUnit.Years), + new Handle(vol), + indexSixMonths, + indexSixMonths.tenor(), + indexSixMonths.dayCounter(), + indexSixMonths.dayCounter(), + rhTermStructure)); + swaptions.Last().addTimesTo(times); + } + + // Building time-grid + TimeGrid grid = new TimeGrid(times, times.Count, 30); + + + // defining the models + G2 modelG2 = new G2(rhTermStructure); + HullWhite modelHw = new HullWhite(rhTermStructure); + HullWhite modelHw2 = new HullWhite(rhTermStructure); + BlackKarasinski modelBk = new BlackKarasinski(rhTermStructure); + + + // model calibrations + + Console.WriteLine("G2 (analytic formulae) calibration"); + for (int i = 0; i < swaptions.Count; i++) + swaptions[i].setPricingEngine(new G2SwaptionEngine(modelG2, 6.0, 16)); + CalibrateModel(modelG2, swaptions); + Console.WriteLine("calibrated to:\n" + + "a = {0:0.000000}, " + + "sigma = {1:0.0000000}\n" + + "b = {2:0.000000}, " + + "eta = {3:0.0000000}\n" + + "rho = {4:0.00000}\n", + modelG2.parameters()[0], + modelG2.parameters()[1], + modelG2.parameters()[2], + modelG2.parameters()[3], + modelG2.parameters()[4]); + + Console.WriteLine("Hull-White (analytic formulae) calibration"); + for (int i = 0; i < swaptions.Count; i++) + swaptions[i].setPricingEngine(new JamshidianSwaptionEngine(modelHw)); + CalibrateModel(modelHw, swaptions); + Console.WriteLine("calibrated to:\n" + + "a = {0:0.000000}, " + + "sigma = {1:0.0000000}\n", + modelHw.parameters()[0], + modelHw.parameters()[1]); + + Console.WriteLine("Hull-White (numerical) calibration"); + for (int i = 0; i < swaptions.Count(); i++) + swaptions[i].setPricingEngine(new TreeSwaptionEngine(modelHw2, grid)); + CalibrateModel(modelHw2, swaptions); + Console.WriteLine("calibrated to:\n" + + "a = {0:0.000000}, " + + "sigma = {1:0.0000000}\n", + modelHw2.parameters()[0], + modelHw2.parameters()[1]); + + Console.WriteLine("Black-Karasinski (numerical) calibration"); + for (int i = 0; i < swaptions.Count; i++) + swaptions[i].setPricingEngine(new TreeSwaptionEngine(modelBk, grid)); + CalibrateModel(modelBk, swaptions); + Console.WriteLine("calibrated to:\n" + + "a = {0:0.000000}, " + + "sigma = {1:0.00000}\n", + modelBk.parameters()[0], + modelBk.parameters()[1]); + + + // ATM Bermudan swaption pricing + Console.WriteLine("Payer bermudan swaption " + + "struck at {0:0.00000 %} (ATM)", + fixedAtmRate); + + List bermudanDates = new List(); + List leg = swap.fixedLeg(); + for (int i = 0; i < leg.Count; i++) + { + Coupon coupon = (Coupon)leg[i]; + bermudanDates.Add(coupon.accrualStartDate()); + } + + Exercise bermudanExercise = new BermudanExercise(bermudanDates); + + Swaption bermudanSwaption = new Swaption(atmSwap, bermudanExercise); + + // Do the pricing for each model + + // G2 price the European swaption here, it should switch to bermudan + bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelG2, 50)); + Console.WriteLine("G2: {0:0.00}", bermudanSwaption.NPV()); + + bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw, 50)); + Console.WriteLine("HW: {0:0.000}", bermudanSwaption.NPV()); + + bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw2, 50)); + Console.WriteLine("HW (num): {0:0.000}", bermudanSwaption.NPV()); + + bermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelBk, 50)); + Console.WriteLine("BK: {0:0.000}", bermudanSwaption.NPV()); + + + // OTM Bermudan swaption pricing + Console.WriteLine("Payer bermudan swaption " + + "struck at {0:0.00000 %} (OTM)", + fixedOtmRate); + + Swaption otmBermudanSwaption = new Swaption(otmSwap, bermudanExercise); + + // Do the pricing for each model + otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelG2, 50)); + Console.WriteLine("G2: {0:0.0000}", otmBermudanSwaption.NPV()); + + otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw, 50)); + Console.WriteLine("HW: {0:0.0000}", otmBermudanSwaption.NPV()); + + otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw2, 50)); + Console.WriteLine("HW (num): {0:0.000}", otmBermudanSwaption.NPV()); + + otmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelBk, 50)); + Console.WriteLine("BK: {0:0.0000}", otmBermudanSwaption.NPV()); + + // ITM Bermudan swaption pricing + Console.WriteLine("Payer bermudan swaption " + + "struck at {0:0.00000 %} (ITM)", + fixedItmRate); + + Swaption itmBermudanSwaption = new Swaption(itmSwap, bermudanExercise); + + // Do the pricing for each model + itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelG2, 50)); + Console.WriteLine("G2: {0:0.000}", itmBermudanSwaption.NPV()); + + itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw, 50)); + Console.WriteLine("HW: {0:0.000}", itmBermudanSwaption.NPV()); + + itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelHw2, 50)); + Console.WriteLine("HW (num): {0:0.000}", itmBermudanSwaption.NPV()); + + itmBermudanSwaption.setPricingEngine(new TreeSwaptionEngine(modelBk, 50)); + Console.WriteLine("BK: {0:0.000}", itmBermudanSwaption.NPV()); + + + Console.WriteLine(" \nRun completed in {0}", DateTime.Now - timer); + Console.WriteLine(); + + Console.Write("Press any key to continue ..."); + Console.ReadKey(); + } + } +} diff --git a/Examples/BermudanSwaption/Properties/AssemblyInfo.cs b/Examples/BermudanSwaption/Properties/AssemblyInfo.cs index 6b9816244..863722844 100644 --- a/Examples/BermudanSwaption/Properties/AssemblyInfo.cs +++ b/Examples/BermudanSwaption/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // È possibile specificare tutti i valori oppure impostare i valori predefiniti per i numeri relativi alla build e alla revisione // utilizzando l'asterisco (*) come descritto di seguito: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.8.0.0")] -[assembly: AssemblyFileVersion("1.8.0.0")] +[assembly: AssemblyVersion("1.9.0.0")] +[assembly: AssemblyFileVersion("1.9.0.0")] diff --git a/Examples/Bonds/Properties/AssemblyInfo.cs b/Examples/Bonds/Properties/AssemblyInfo.cs index 9fe0fa58e..8767afe26 100644 --- a/Examples/Bonds/Properties/AssemblyInfo.cs +++ b/Examples/Bonds/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.8.0.0")] -[assembly: AssemblyFileVersion("1.8.0.0")] +[assembly: AssemblyVersion("1.9.0.0")] +[assembly: AssemblyFileVersion("1.9.0.0")] diff --git a/Examples/CVAIRS/Properties/AssemblyInfo.cs b/Examples/CVAIRS/Properties/AssemblyInfo.cs index 3eaa44bb9..5c1f25930 100644 --- a/Examples/CVAIRS/Properties/AssemblyInfo.cs +++ b/Examples/CVAIRS/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration( "" )] [assembly: AssemblyCompany( "" )] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark( "" )] [assembly: AssemblyCulture( "" )] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.8.0.0" )] -[assembly: AssemblyFileVersion( "1.8.0.0" )] +[assembly: AssemblyVersion( "1.9.0.0" )] +[assembly: AssemblyFileVersion( "1.9.0.0" )] diff --git a/Examples/CallableBonds/Properties/AssemblyInfo.cs b/Examples/CallableBonds/Properties/AssemblyInfo.cs index aff426270..a487d8817 100644 --- a/Examples/CallableBonds/Properties/AssemblyInfo.cs +++ b/Examples/CallableBonds/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // È possibile specificare tutti i valori oppure impostare valori predefiniti per i numeri relativi alla revisione e alla build // utilizzando l'asterisco (*) come descritto di seguito: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.8.0.0")] -[assembly: AssemblyFileVersion("1.8.0.0")] +[assembly: AssemblyVersion("1.9.0.0")] +[assembly: AssemblyFileVersion("1.9.0.0")] diff --git a/Examples/EquityOption/Properties/AssemblyInfo.cs b/Examples/EquityOption/Properties/AssemblyInfo.cs index cb15ea6f6..1b3c06774 100644 --- a/Examples/EquityOption/Properties/AssemblyInfo.cs +++ b/Examples/EquityOption/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.0.0")] -[assembly: AssemblyFileVersion("1.7.0.0")] +[assembly: AssemblyVersion("1.9.0.0")] +[assembly: AssemblyFileVersion("1.9.0.0")] diff --git a/Examples/FRA/Properties/AssemblyInfo.cs b/Examples/FRA/Properties/AssemblyInfo.cs index 480370704..dbc1a070f 100644 --- a/Examples/FRA/Properties/AssemblyInfo.cs +++ b/Examples/FRA/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // È possibile specificare tutti i valori oppure impostare i valori predefiniti per i numeri relativi alla build e alla revisione // utilizzando l'asterisco (*) come descritto di seguito: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.0.0")] -[assembly: AssemblyFileVersion("1.7.0.0")] +[assembly: AssemblyVersion("1.9.0.0")] +[assembly: AssemblyFileVersion("1.9.0.0")] diff --git a/Examples/FittedBondCurve/Properties/AssemblyInfo.cs b/Examples/FittedBondCurve/Properties/AssemblyInfo.cs index 54889acd3..7b1d1a718 100644 --- a/Examples/FittedBondCurve/Properties/AssemblyInfo.cs +++ b/Examples/FittedBondCurve/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration( "" )] [assembly: AssemblyCompany( "" )] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark( "" )] [assembly: AssemblyCulture( "" )] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.7.0.0" )] -[assembly: AssemblyFileVersion( "1.7.0.0" )] +[assembly: AssemblyVersion( "1.9.0.0" )] +[assembly: AssemblyFileVersion( "1.9.0.0" )] diff --git a/Examples/Repo/Properties/AssemblyInfo.cs b/Examples/Repo/Properties/AssemblyInfo.cs index 562bc1eeb..cb1153aaa 100644 --- a/Examples/Repo/Properties/AssemblyInfo.cs +++ b/Examples/Repo/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.0.0")] -[assembly: AssemblyFileVersion("1.7.0.0")] +[assembly: AssemblyVersion("1.9.0.0")] +[assembly: AssemblyFileVersion("1.9.0.0")] diff --git a/Examples/Swap/Properties/AssemblyInfo.cs b/Examples/Swap/Properties/AssemblyInfo.cs index 1f2e10818..6f8caf9ef 100644 --- a/Examples/Swap/Properties/AssemblyInfo.cs +++ b/Examples/Swap/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct( "QLNet Examples" )] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.0.0")] -[assembly: AssemblyFileVersion("1.7.0.0")] +[assembly: AssemblyVersion("1.9.0.0")] +[assembly: AssemblyFileVersion("1.9.0.0")] diff --git a/News.txt b/News.txt index a3c7ac620..3af994eed 100644 --- a/News.txt +++ b/News.txt @@ -1,42 +1,75 @@ -QLNet 1.8 +QLNet 1.9 ========================= -QLNet 1.8 stable version. +QLNet 1.9 stable version. The most notable changes are included below. A detailed list of changes is available in ChangeLog.txt. FRAMEWORK -+ Refactored code to be compatible with .NET Core , created VS 2015 solution QLNet_Core.sln - that build the .NET Core version. - -+ Refactored test project to work with Microsoft UnitTesting and Xunit. ++ Refactoring & Update BlackScholesProcess, LocalVolCurve, bsmlattice ++ Optimized npvbps calculation ++ List and InitializedList refactoring -INTEREST RATES +CASHFLOWS -+ Fixed links to documentation for LIBOR indexes. ++ Fixed CappedFlooredCoupon factory INSTRUMENTS -+ Added basic CVA IRS pricing engine. ++ Added CompositeInstrument. ++ Added DividendBarrierOption. ++ Added ForwardVanillaOption. ++ Added LookbackOption. ++ Added CMS Helper. ++ Added BarrierOption. ++ Added Cliquet Option. ++ Added DoubleBarrier Option. ++ Added CPICapFloor. ++ Added CPISwap. -CURRENCIES +DATE/TIME -+ Added Ukrainian hryvnia. ++ Added ECB dates for 2017. ++ Fixed rule for the Japanese Mountain Day holiday. ++ Fixed United States holidays before 1971. -DATE/TIME +INDEXES -+ Added new Ukrainian holiday, Defender's Day. ++ Added Ibor indexes : Aonia , Bbsw, Bkbm and Nzocr. MATH -+ Added mixed log interpolation. -+ Added FlatExtrapolator2D -+ Added BackwardflatLinear Interpolation. -+ Added AbcdInterpolation. -+ Implemented Lagrange boundary condition for cubic interpolation ++ Added Matrix inverse calculation with Crout's LU decomposition. ++ Added VannaVolga Interpolation. + +TERMSTRUCTURES + ++ Added Swaption volatility cube. ++ Allow negative jumps in yield term structures. PRICING ENGINES -+ Added CounterpartyAdjSwapEngine engine with example program. ++ Added ForwardVanillaEngine engine. ++ Added AnalyticContinuousFixedLookbackEngine engine. ++ Added AnalyticContinuousFloatingLookbackEngine engine. ++ Added AnalyticContinuousPartialFixedLookbackEngine engine. ++ Added AnalyticContinuousPartialFloatingLookbackEngine engine. ++ Added AnalyticBinaryBarrierEngine. ++ Added AnalyticCliquetEngine. ++ Added AnalyticPerformanceEngine. ++ Added BlackDeltaCalculator and DeltaVolQuote. ++ Added VannaVolga BarrierEngine. ++ Added AnalyticDoubleBarrierEngine. ++ Added VannaVolgaDoubleBarrierEngine. ++ Added WulinYongDoubleBarrierEngine. ++ Added InterpolatingCPICapFloorEngine. + +TESTS + ++ Added theta pertubation in AmericanOption & DividendOption tests. ++ Added tests for China SSE and IB calendars and a missing Chinese holiday ++ Added Test : Chambers-Nawalkha implied vol approximation ++ Added CapFloored coupon tests. ++ Added Digital Coupon tests. diff --git a/QLNet/Cashflows/CappedFlooredCoupon.cs b/QLNet/Cashflows/CappedFlooredCoupon.cs index 412f90844..cd7426cc8 100644 --- a/QLNet/Cashflows/CappedFlooredCoupon.cs +++ b/QLNet/Cashflows/CappedFlooredCoupon.cs @@ -1,7 +1,7 @@ /* Copyright (C) 2008 Toyin Akin (toyin_akin@hotmail.com) Copyright (C) 2009 Siarhei Novik (snovik@gmail.com) - Copyright (C) 2008, 2009 , 2010 Andrea Maggiulli (a.maggiulli@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -165,7 +165,7 @@ public override void setPricer(FloatingRateCouponPricer pricer) // Factory - for Leg generators public virtual CashFlow factory(double nominal, Date paymentDate, Date startDate, Date endDate, int fixingDays, InterestRateIndex index, double gearing, double spread, double? cap, double? floor, Date refPeriodStart, Date refPeriodEnd, DayCounter dayCounter, bool isInArrears) { - return new CappedFlooredCoupon( new FloatingRateCoupon( paymentDate, nominal, startDate, endDate, fixingDays, index, gearing, spread, refPeriodStart, refPeriodEnd, dayCounter, isInArrears ), cap, floor ); + return new CappedFlooredCoupon( new IborCoupon( paymentDate, nominal, startDate, endDate, fixingDays, (IborIndex )index, gearing, spread, refPeriodStart, refPeriodEnd, dayCounter, isInArrears ), cap, floor ); } } diff --git a/QLNet/Cashflows/CashFlows.cs b/QLNet/Cashflows/CashFlows.cs index 83ae6daff..f1f5cb99b 100644 --- a/QLNet/Cashflows/CashFlows.cs +++ b/QLNet/Cashflows/CashFlows.cs @@ -1,6 +1,6 @@ /* Copyright (C) 2008, 2009 Siarhei Novik (snovik@gmail.com) - Copyright (C) 2008-2013 Andrea Maggiulli (a.maggiulli@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -360,9 +360,9 @@ public BPSCalculator(YieldTermStructure discountCurve) // visitor classes should implement the generic visit method in the following form public void visit(object o) { - Type[] types = new Type[] { o.GetType() }; + Type[] types = new Type[] { o.GetType() }; MethodInfo methodInfo = Utils.GetMethodInfo( this, "visit", types ); - + if (methodInfo != null) { methodInfo.Invoke(this, new object[] { o }); } @@ -726,27 +726,29 @@ public static double bps(Leg leg, YieldTermStructure discountCurve, bool include public static void npvbps(Leg leg,YieldTermStructure discountCurve, bool includeSettlementDateFlows, Date settlementDate, Date npvDate, out double npv,out double bps) { - npv = 0.0; + npv = bps = 0.0; if (leg.empty()) { bps = 0.0; return; } - BPSCalculator calc = new BPSCalculator(discountCurve); for (int i=0; i swaptionVol, GFunctionFactory.YieldCurveModel modelOfYieldCurve, Handle meanReversion, double lowerLimit, double upperLimit, double precision) + public double hardUpperLimit_; + + public NumericHaganPricer(Handle swaptionVol, + GFunctionFactory.YieldCurveModel modelOfYieldCurve, + Handle meanReversion, + double lowerLimit = 0.0, + double upperLimit = 1.0, + double precision = 1.0e-6, + double hardUpperLimit = Double.MaxValue) : base(swaptionVol, modelOfYieldCurve, meanReversion) { upperLimit_ = upperLimit; lowerLimit_ = lowerLimit; requiredStdDeviations_ = 8; precision_ = precision; refiningIntegrationTolerance_ = 0.0001; + hardUpperLimit_ = hardUpperLimit; } protected override double optionletPrice(Option.Type optionType, double strike) { @@ -700,6 +709,7 @@ public double integrate(double a, double b, ConundrumIntegrand integrand) { GaussKronrodNonAdaptive gaussKronrodNonAdaptive = new GaussKronrodNonAdaptive(precision_, 1000000, 1.0); // if the integration intervall is wide enough we use the // following change variable x -> a + (b-a)*(t/(a-b))^3 + upperBoundary = Math.Max(a,Math.Min(upperBoundary, hardUpperLimit_)); if (upperBoundary > 2 * a) { VariableChange variableChange = new VariableChange(integrand.value, a, upperBoundary, 3); result = gaussKronrodNonAdaptive.value(variableChange.value, .0, 1.0); @@ -709,12 +719,14 @@ public double integrate(double a, double b, ConundrumIntegrand integrand) { // if the expected precision has not been reached we use the old algorithm if (!gaussKronrodNonAdaptive.integrationSuccess()) { - GaussKronrodAdaptive integrator = new GaussKronrodAdaptive(precision_, 1000000); + GaussKronrodAdaptive integrator = new GaussKronrodAdaptive(precision_, 100000); + b = Math.Max(a,Math.Min(b, hardUpperLimit_)); result = integrator.value(integrand.value, a, b); } } // if a < b we use the old algorithm else { - GaussKronrodAdaptive integrator = new GaussKronrodAdaptive(precision_, 1000000); + b = Math.Max(a,Math.Min(b,hardUpperLimit_)); + GaussKronrodAdaptive integrator = new GaussKronrodAdaptive(precision_, 100000); result = integrator.value(integrand.value, a, b); } return result; @@ -755,13 +767,12 @@ public double refineIntegration(double integralValue, ConundrumIntegrand integra #region Nested classes public class VariableChange { - private double a_, b_, width_; + private double a_, width_; private Func f_; private int k_; public VariableChange(Func f, double a, double b, int k) { a_ = a; - b_ = b; width_ = b - a; f_ = f; k_ = k; diff --git a/QLNet/Cashflows/LinearTsrPricer.cs b/QLNet/Cashflows/LinearTsrPricer.cs new file mode 100644 index 000000000..cdfc70a2e --- /dev/null +++ b/QLNet/Cashflows/LinearTsrPricer.cs @@ -0,0 +1,556 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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.Linq; + +namespace QLNet +{ + //! CMS-coupon pricer + /*! Prices a cms coupon using a linear terminal swap rate model + The slope parameter is linked to a gaussian short rate model. + Reference: Andersen, Piterbarg, Interest Rate Modeling, 16.3.2 + + The cut off point for integration can be set + - by explicitly specifying the lower and upper bound + - by defining the lower and upper bound to be the strike where + a vanilla swaption has 1% or less vega of the atm swaption + - by defining the lower and upper bound to be the strike where + undeflated (!) payer resp. receiver prices are below a given + threshold + - by specificying a number of standard deviations to cover + using a Black Scholes process with an atm volatility as + a benchmark + In every case the lower and upper bound are applied though. + In case the smile section is shifted lognormal, the specified + lower and upper bound are applied to strike + shift so that + e.g. a zero lower bound always refers to the lower bound of + the rates in the shifted lognormal model. + Note that for normal volatility input the lower rate bound + should probably be adjusted to an appropriate negative value, + there is no automatic adjustment in this case. + */ + public class LinearTsrPricer : CmsCouponPricer, IMeanRevertingPricer + { + public class Settings + { + public Settings() + { + strategy_ = Strategy.RateBound; + vegaRatio_ = 0.01; + priceThreshold_ = 1.0E-8; + stdDevs_ = 3.0; + lowerRateBound_ = 0.0001; + upperRateBound_ = 2.0000; + } + + public Settings withRateBound( double lowerRateBound = 0.0001, double upperRateBound = 2.0000 ) + { + strategy_ = Strategy.RateBound; + lowerRateBound_ = lowerRateBound; + upperRateBound_ = upperRateBound; + return this; + } + + public Settings withVegaRatio( double vegaRatio = 0.01, double lowerRateBound = 0.0001, double upperRateBound = 2.0000 ) + { + strategy_ = Strategy.VegaRatio; + vegaRatio_ = vegaRatio; + lowerRateBound_ = lowerRateBound; + upperRateBound_ = upperRateBound; + return this; + } + + public Settings withPriceThreshold( double priceThreshold = 1.0E-8, double lowerRateBound = 0.0001, + double upperRateBound = 2.0000) + { + strategy_ = Strategy.PriceThreshold; + priceThreshold_ = priceThreshold; + lowerRateBound_ = lowerRateBound; + upperRateBound_ = upperRateBound; + return this; + } + + public Settings withBSStdDevs( double stdDevs = 3.0, + double lowerRateBound = 0.0001, + double upperRateBound = 2.0000) + { + strategy_ = Strategy.BSStdDevs; + stdDevs_ = stdDevs; + lowerRateBound_ = lowerRateBound; + upperRateBound_ = upperRateBound; + return this; + } + + public enum Strategy + { + RateBound, + VegaRatio, + PriceThreshold, + BSStdDevs + } + + public Strategy strategy_; + public double vegaRatio_; + public double priceThreshold_; + public double stdDevs_; + public double lowerRateBound_, upperRateBound_; + + } + + public LinearTsrPricer( Handle swaptionVol, + Handle meanReversion, + Handle couponDiscountCurve = null, + Settings settings = null, + Integrator integrator = null) + :base(swaptionVol) + { + meanReversion_ = meanReversion; + couponDiscountCurve_ = couponDiscountCurve ?? new Handle(); + settings_ = settings ?? new Settings(); + volDayCounter_ = swaptionVol.link.dayCounter(); + integrator_ = integrator; + + if (!couponDiscountCurve_.empty()) + couponDiscountCurve_.registerWith(update); + + if (integrator_ == null) + integrator_ = new GaussKronrodNonAdaptive(1E-10, 5000, 1E-10); + } + + /* */ + public override double swapletPrice() + { + if (fixingDate_ <= today_) + { + // the fixing is determined + double Rs = coupon_.swapIndex().fixing(fixingDate_); + double price = + (gearing_ * Rs + spread_) * + (coupon_.accrualPeriod() * + discountCurve_.link.discount(paymentDate_) * couponDiscountRatio_); + return price; + } + else + { + double atmCapletPrice = optionletPrice(Option.Type.Call, swapRateValue_); + double atmFloorletPrice = optionletPrice(Option.Type.Put, swapRateValue_); + return gearing_ * (coupon_.accrualPeriod() * + discountCurve_.link.discount(paymentDate_) * + swapRateValue_ * couponDiscountRatio_ + + atmCapletPrice - atmFloorletPrice) + + spreadLegValue_; + } + } + public override double swapletRate() + { + return swapletPrice() / + ( coupon_.accrualPeriod() * discountCurve_.link.discount( paymentDate_ ) * couponDiscountRatio_ ); + } + public override double capletPrice(double effectiveCap) + { + // caplet is equivalent to call option on fixing + if (fixingDate_ <= today_) + { + // the fixing is determined + double Rs = Math.Max(coupon_.swapIndex().fixing(fixingDate_) - effectiveCap, 0.0); + double price = + (gearing_ * Rs) * + (coupon_.accrualPeriod() * + discountCurve_.link.discount(paymentDate_) * couponDiscountRatio_); + return price; + } + else + { + double capletPrice = optionletPrice(Option.Type.Call, effectiveCap); + return gearing_ * capletPrice; + } + } + public override double capletRate(double effectiveCap) + { + return capletPrice( effectiveCap ) / + ( coupon_.accrualPeriod() * + discountCurve_.link.discount( paymentDate_ ) * couponDiscountRatio_ ); + } + public override double floorletPrice(double effectiveFloor) + { + // floorlet is equivalent to put option on fixing + if (fixingDate_ <= today_) + { + // the fixing is determined + double Rs = Math.Max(effectiveFloor - coupon_.swapIndex().fixing(fixingDate_), 0.0); + double price = + (gearing_ * Rs) * + (coupon_.accrualPeriod() * + discountCurve_.link.discount(paymentDate_) * couponDiscountRatio_); + return price; + } + else + { + double floorletPrice = optionletPrice(Option.Type.Put, effectiveFloor); + return gearing_ * floorletPrice; + } + } + public override double floorletRate(double effectiveFloor) + { + return floorletPrice( effectiveFloor ) / + ( coupon_.accrualPeriod() * + discountCurve_.link.discount( paymentDate_ ) * couponDiscountRatio_ ); + } + /* */ + public double meanReversion() { return meanReversion_.link.value(); } + public void setMeanReversion( Handle meanReversion) + { + meanReversion_.unregisterWith(update); + meanReversion_ = meanReversion; + meanReversion_.registerWith(update); + update(); + } + + + private double GsrG( Date d) + { + double yf = volDayCounter_.yearFraction(fixingDate_, d); + if (Math.Abs(meanReversion_.link.value()) < 1.0E-4) + return yf; + else + return (1.0 - Math.Exp(-meanReversion_.link.value() * yf)) / + meanReversion_.link.value(); + } + private double singularTerms( Option.Type type, double strike) + { + double omega = (type == Option.Type.Call ? 1.0 : -1.0); + double s1 = Math.Max(omega * (swapRateValue_ - strike), 0.0) * + (a_ * swapRateValue_ + b_); + double s2 = (a_ * strike + b_) * + smileSection_.optionPrice(strike, strike < swapRateValue_ ? Option.Type.Put : Option.Type.Call); + return s1 + s2; + } + private double integrand(double strike) + { + return 2.0 * a_ * smileSection_.optionPrice( strike, strike < swapRateValue_ ? Option.Type.Put : Option.Type.Call); + } + private double a_, b_; + + private class VegaRatioHelper : ISolver1d + { + public VegaRatioHelper( SmileSection section, double targetVega) + { + section_ = section; + targetVega_ = targetVega; + } + + public override double value(double strike) + { + return section_.vega(strike) - targetVega_; + } + + SmileSection section_; + double targetVega_; + } + + private class PriceHelper : ISolver1d + { + public PriceHelper( SmileSection section, Option.Type type,double targetPrice) + { + section_ = section; + targetPrice_ = targetPrice; + type_ = type; + } + + public override double value(double strike) + { + return section_.optionPrice(strike, type_) - targetPrice_; + } + + private SmileSection section_; + private double targetPrice_; + private Option.Type type_; + } + + public override void initialize( FloatingRateCoupon coupon) + { + coupon_ = coupon as CmsCoupon; + Utils.QL_REQUIRE(coupon_ != null,()=> "CMS coupon needed"); + gearing_ = coupon_.gearing(); + spread_ = coupon_.spread(); + + fixingDate_ = coupon_.fixingDate(); + paymentDate_ = coupon_.date(); + swapIndex_ = coupon_.swapIndex(); + + forwardCurve_ = swapIndex_.forwardingTermStructure(); + if (swapIndex_.exogenousDiscount()) + discountCurve_ = swapIndex_.discountingTermStructure(); + else + discountCurve_ = forwardCurve_; + + // if no coupon discount curve is given just use the discounting curve + // from the swap index. for rate calculation this curve cancels out in + // the computation, so e.g. the discounting swap engine will produce + // correct results, even if the couponDiscountCurve is not set here. + // only the price member function in this class will be dependent on the + // coupon discount curve. + + today_ = QLNet.Settings.evaluationDate(); + + if (paymentDate_ > today_ && !couponDiscountCurve_.empty()) + couponDiscountRatio_ = couponDiscountCurve_.link.discount(paymentDate_) / + discountCurve_.link.discount(paymentDate_); + else + couponDiscountRatio_ = 1.0; + + spreadLegValue_ = spread_ * coupon_.accrualPeriod() * + discountCurve_.link.discount(paymentDate_) * + couponDiscountRatio_; + + if (fixingDate_ > today_) + { + swapTenor_ = swapIndex_.tenor(); + swap_ = swapIndex_.underlyingSwap(fixingDate_); + + swapRateValue_ = swap_.fairRate(); + annuity_ = 1.0E4 * Math.Abs(swap_.fixedLegBPS()); + + SmileSection sectionTmp = swaptionVolatility().link.smileSection(fixingDate_, swapTenor_); + + // adjust bounds by section's shift + shiftedLowerBound_ = settings_.lowerRateBound_ - sectionTmp.shift(); + shiftedUpperBound_ = settings_.upperRateBound_ - sectionTmp.shift(); + + // if the section does not provide an atm level, we enhance it to + // have one, no need to exit with an exception ... + + if (sectionTmp.atmLevel() ==null) + smileSection_ = new AtmSmileSection(sectionTmp, swapRateValue_); + else + smileSection_ = sectionTmp; + + // compute linear model's parameters + + double gx = 0.0, gy = 0.0; + for (int i = 0; i < swap_.fixedLeg().Count; i++) + { + Coupon c = swap_.fixedLeg()[i] as Coupon; + double yf = c.accrualPeriod(); + Date d = c.date(); + double pv = yf * discountCurve_.link.discount(d); + gx += pv * GsrG(d); + gy += pv; + } + + double gamma = gx / gy; + Date lastd = swap_.fixedLeg().Last().date(); + + a_ = discountCurve_.link.discount(paymentDate_) * + (gamma - GsrG(paymentDate_)) / + (discountCurve_.link.discount(lastd) * GsrG(lastd) + + swapRateValue_ * gy * gamma); + + b_ = discountCurve_.link.discount(paymentDate_) / gy - + a_ * swapRateValue_; + } + + } + + private double optionletPrice(Option.Type optionType, double strike) + { + if (optionType == Option.Type.Call && strike >= shiftedUpperBound_) + return 0.0; + if (optionType == Option.Type.Put && strike <= shiftedLowerBound_) + return 0.0; + + // determine lower or upper integration bound (depending on option type) + + double lower = strike, upper = strike; + + switch (settings_.strategy_) + { + + case Settings.Strategy.RateBound: + { + if (optionType == Option.Type.Call) + upper = shiftedUpperBound_; + else + lower = shiftedLowerBound_; + break; + } + + case Settings.Strategy.VegaRatio: + { + // strikeFromVegaRatio ensures that returned strike is on the + // expected side of strike + double bound = strikeFromVegaRatio(settings_.vegaRatio_, optionType, strike); + if (optionType == Option.Type.Call) + upper = Math.Min(bound, shiftedUpperBound_); + else + lower = Math.Max(bound, shiftedLowerBound_); + break; + } + + case Settings.Strategy.PriceThreshold: + { + // strikeFromPrice ensures that returned strike is on the expected + // side of strike + double bound = strikeFromPrice(settings_.vegaRatio_, optionType, strike); + if (optionType == Option.Type.Call) + upper = Math.Min(bound, shiftedUpperBound_); + else + lower = Math.Max(bound, shiftedLowerBound_); + break; + } + + case Settings.Strategy.BSStdDevs : + { + double? atm = smileSection_.atmLevel(); + double atmVol = smileSection_.volatility(atm.GetValueOrDefault()); + double shift = smileSection_.shift(); + double lowerTmp, upperTmp; + if (smileSection_.volatilityType() == VolatilityType.ShiftedLognormal) + { + upperTmp = (atm.GetValueOrDefault() + shift) * + Math.Exp(settings_.stdDevs_ * atmVol - + 0.5 * atmVol * atmVol * + smileSection_.exerciseTime()) - shift; + lowerTmp = (atm.GetValueOrDefault() + shift) * + Math.Exp(-settings_.stdDevs_ * atmVol - + 0.5 * atmVol * atmVol * + smileSection_.exerciseTime()) - shift; + } + else + { + double tmp = settings_.stdDevs_ * atmVol * Math.Sqrt(smileSection_.exerciseTime()); + upperTmp = atm.GetValueOrDefault() + tmp; + lowerTmp = atm.GetValueOrDefault() - tmp; + } + upper = Math.Min(upperTmp - shift, shiftedUpperBound_); + lower = Math.Max(lowerTmp - shift, shiftedLowerBound_); + break; + } + + default: + Utils.QL_FAIL("Unknown strategy (" + settings_.strategy_ + ")"); + break; + } + + // compute the relevant integral + + double result = 0.0; + double tmpBound; + if (upper > lower) + { + tmpBound = Math.Min(upper, swapRateValue_); + if (tmpBound > lower) + { + result += integrator_.value( integrand,lower, tmpBound); + } + tmpBound = Math.Max(lower, swapRateValue_); + if (upper > tmpBound) + { + result += integrator_.value(integrand,tmpBound, upper); + } + result *= (optionType == Option.Type.Call ? 1.0 : -1.0); + } + + result += singularTerms(optionType, strike); + + return annuity_ * result * couponDiscountRatio_ * coupon_.accrualPeriod(); + } + + private double strikeFromVegaRatio(double ratio, Option.Type optionType,double referenceStrike) + { + double a, b, min, max, k; + if (optionType == Option.Type.Call) + { + a = swapRateValue_; + min = referenceStrike; + b = max = k = Math.Min(smileSection_.maxStrike(), shiftedUpperBound_); + } + else + { + a = min = k = Math.Max(smileSection_.minStrike(), shiftedLowerBound_); + b = swapRateValue_; + max = referenceStrike; + } + + VegaRatioHelper h = new VegaRatioHelper(smileSection_,smileSection_.vega(swapRateValue_) * ratio); + Brent solver = new Brent(); + + try + { + k = solver.solve(h, 1.0E-5, (a + b) / 2.0, a, b); + } + catch (Exception) + { + // use default value set above + } + + return Math.Min(Math.Max(k, min), max); + } + + private double strikeFromPrice(double price, Option.Type optionType,double referenceStrike) + { + double a, b, min, max, k; + if (optionType == Option.Type.Call) + { + a = swapRateValue_; + min = referenceStrike; + b = max = k = Math.Min(smileSection_.maxStrike(), shiftedUpperBound_); + } + else + { + a = min = k = Math.Max(smileSection_.minStrike(), shiftedLowerBound_); + b = swapRateValue_; + max = referenceStrike; + } + + PriceHelper h = new PriceHelper(smileSection_, optionType, price); + Brent solver = new Brent(); + + try + { + k = solver.solve(h, 1.0E-5, swapRateValue_, a, b); + } + catch (Exception) + { + // use default value set above + } + + return Math.Min(Math.Max(k, min), max); + + } + + private Handle meanReversion_; + private Handle forwardCurve_, discountCurve_; + private Handle couponDiscountCurve_; + private CmsCoupon coupon_; + private Date today_, paymentDate_, fixingDate_; + private double gearing_, spread_; + + private Period swapTenor_; + private double spreadLegValue_, swapRateValue_, couponDiscountRatio_, annuity_; + + private SwapIndex swapIndex_; + private VanillaSwap swap_; + private SmileSection smileSection_; + + private Settings settings_; + private DayCounter volDayCounter_; + private Integrator integrator_; + + private double shiftedLowerBound_, shiftedUpperBound_; + + } +} diff --git a/QLNet/Extensions/ListExtension.cs b/QLNet/Extensions/ListExtension.cs index dddb362ae..dffde2ec4 100644 --- a/QLNet/Extensions/ListExtension.cs +++ b/QLNet/Extensions/ListExtension.cs @@ -27,5 +27,12 @@ public static class ListExtension list.AddRange( Enumerable.Repeat( element, size - count ) ); } } + + // erases the contents without changing the size + public static void Erase( this List list ) + { + for ( int i = 0; i < list.Count; i++ ) + list[i] = default( T ); + } } } diff --git a/QLNet/Indexes/Ibor/Aonia.cs b/QLNet/Indexes/Ibor/Aonia.cs new file mode 100644 index 000000000..f8c85c9f3 --- /dev/null +++ b/QLNet/Indexes/Ibor/Aonia.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + //! %Aonia index + /*! Aonia (Australia Overnight Index Average) rate fixed by the RBA. + + See . + */ + public class Aonia : OvernightIndex + { + public Aonia( Handle h = null) + : base("Aonia", 0, new AUDCurrency(), new Australia(), new Actual365Fixed(), + h ?? new Handle()) + {} + } +} diff --git a/QLNet/Indexes/Ibor/Bbsw.cs b/QLNet/Indexes/Ibor/Bbsw.cs new file mode 100644 index 000000000..318bd9972 --- /dev/null +++ b/QLNet/Indexes/Ibor/Bbsw.cs @@ -0,0 +1,85 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + //! %Bbsw index + /*! Bbsw rate fixed by AFMA. + + See . + */ + public class Bbsw : IborIndex + { + public Bbsw( Period tenor, Handle h = null) + : base("Bbsw", tenor, + 0, // settlement days + new AUDCurrency(), new Australia(), + BusinessDayConvention.HalfMonthModifiedFollowing, true, + new Actual365Fixed(), h ?? new Handle()) + { + Utils.QL_REQUIRE(this.tenor().units() != TimeUnit.Days,()=> + "for daily tenors (" + this.tenor() + ") dedicated DailyTenor constructor must be used"); + } + } + + //! 1-month %Bbsw index + public class Bbsw1M : Bbsw + { + public Bbsw1M( Handle h = null) + : base(new Period(1, TimeUnit.Months), h ?? new Handle()) + {} + } + + //! 2-month %Bbsw index + public class Bbsw2M : Bbsw + { + public Bbsw2M( Handle h = null ) + : base( new Period( 2, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 3-month %Bbsw index + public class Bbsw3M : Bbsw + { + public Bbsw3M( Handle h = null ) + : base( new Period( 3, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 4-month %Bbsw index + public class Bbsw4M : Bbsw + { + public Bbsw4M( Handle h = null ) + : base( new Period( 4, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 5-month %Bbsw index + public class Bbsw5M : Bbsw + { + public Bbsw5M( Handle h = null ) + : base( new Period( 5, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 6-month %Bbsw index + public class Bbsw6M : Bbsw + { + public Bbsw6M( Handle h = null ) + : base( new Period( 6, TimeUnit.Months ), h ?? new Handle() ) + { } + } +} diff --git a/QLNet/Indexes/Ibor/Bkbm.cs b/QLNet/Indexes/Ibor/Bkbm.cs new file mode 100644 index 000000000..f1e0c17af --- /dev/null +++ b/QLNet/Indexes/Ibor/Bkbm.cs @@ -0,0 +1,88 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + //! %Bkbm index + /*! Bkbm rate fixed by NZFMA. + + See . + */ + public class Bkbm : IborIndex + { + public Bkbm( Period tenor, Handle h = null) + : base("Bkbm", tenor, + 0, // settlement days + new NZDCurrency(), new NewZealand(), + BusinessDayConvention.ModifiedFollowing, true, + new Actual365Fixed(), h ?? new Handle()) + { + Utils.QL_REQUIRE(this.tenor().units() != TimeUnit.Days,()=> + "for daily tenors (" + this.tenor() + ") dedicated DailyTenor constructor must be used"); + } + } + + //! 1-month %Bkbm index + public class Bkbm1M : Bkbm + { + public Bkbm1M( Handle h = null) + : base(new Period(1, TimeUnit.Months), h ?? new Handle()) + {} + } + + //! 2-month %Bkbm index + public class Bkbm2M : Bkbm + { + public Bkbm2M( Handle h = null ) + : base( new Period( 2, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 3-month %Bkbm index + public class Bkbm3M : Bkbm + { + public Bkbm3M( Handle h = null ) + : base( new Period( 3, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 4-month %Bkbm index + public class Bkbm4M : Bkbm + { + public Bkbm4M( Handle h = null ) + : base( new Period( 4, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 5-month %Bkbm index + public class Bkbm5M : Bkbm + { + public Bkbm5M( Handle h = null ) + : base( new Period( 5, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + //! 6-month %Bkbm index + public class Bkbm6M : Bkbm + { + public Bkbm6M( Handle h = null ) + : base( new Period( 6, TimeUnit.Months ), h ?? new Handle() ) + { } + } + + + +} diff --git a/QLNet/Indexes/Ibor/Nzocr.cs b/QLNet/Indexes/Ibor/Nzocr.cs new file mode 100644 index 000000000..5b2b025f0 --- /dev/null +++ b/QLNet/Indexes/Ibor/Nzocr.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + //! %Nzocr index + /*! %Nzocr (New Zealand official cash rate) rate fixed by the RBNZ. + + See . + */ + public class Nzocr : OvernightIndex + { + public Nzocr( Handle h = null ) + :base("Nzocr", 0, new NZDCurrency(), + new NewZealand(), + new Actual365Fixed(), h ?? new Handle()) + {} + } +} diff --git a/QLNet/Instruments/AsianOption.cs b/QLNet/Instruments/AsianOption.cs index fb3ae3175..e40806acc 100644 --- a/QLNet/Instruments/AsianOption.cs +++ b/QLNet/Instruments/AsianOption.cs @@ -29,7 +29,7 @@ namespace QLNet { // public class ContinuousAveragingAsianOption : OneAssetOption { - new public class Arguments : OneAssetOption.Arguments + public new class Arguments : OneAssetOption.Arguments { public Arguments() { @@ -45,7 +45,7 @@ public override void validate() public Average.Type averageType; } - new public class Engine: GenericEngine + public new class Engine: GenericEngine { } @@ -70,7 +70,7 @@ public override void setupArguments(IPricingEngineArguments args) //! \ingroup instruments public class DiscreteAveragingAsianOption : OneAssetOption { - new public class Arguments : OneAssetOption.Arguments + public new class Arguments : OneAssetOption.Arguments { public Arguments() { @@ -113,7 +113,7 @@ public override void validate() public List fixingDates; } - new public class Engine: GenericEngine + public new class Engine: GenericEngine { } diff --git a/QLNet/Instruments/BarrierOption.cs b/QLNet/Instruments/BarrierOption.cs index b287be8a0..162f2588e 100644 --- a/QLNet/Instruments/BarrierOption.cs +++ b/QLNet/Instruments/BarrierOption.cs @@ -26,7 +26,7 @@ namespace QLNet { // public class BarrierOption : OneAssetOption { - new public class Arguments : OneAssetOption.Arguments + public new class Arguments : OneAssetOption.Arguments { public Arguments() { @@ -61,7 +61,7 @@ public override void validate() } } - new public class Engine : GenericEngine + public new class Engine : GenericEngine { protected bool triggered(double underlying) { diff --git a/QLNet/Instruments/BasisSwap.cs b/QLNet/Instruments/BasisSwap.cs index 347d77fb1..8f75f49ab 100644 --- a/QLNet/Instruments/BasisSwap.cs +++ b/QLNet/Instruments/BasisSwap.cs @@ -230,7 +230,7 @@ public override void fetchResults(IPricingEngineResults r) //! %Arguments for simple swap calculation - new public class Arguments : Swap.Arguments + public new class Arguments : Swap.Arguments { public Type type; public double nominal; diff --git a/QLNet/Instruments/Bonds/BTP.cs b/QLNet/Instruments/Bonds/BTP.cs index 6c6f3d6ad..d9b9910b2 100644 --- a/QLNet/Instruments/Bonds/BTP.cs +++ b/QLNet/Instruments/Bonds/BTP.cs @@ -254,17 +254,17 @@ public List durations() } // swaps public List swapLengths() { return swapLenghts_; } - public InitializedList swapRates() + public List swapRates() { calculate(); return swapRates_; } - public InitializedList swapYields() + public List swapYields() { calculate(); return swapBondYields_; } - public InitializedList swapDurations() + public List swapDurations() { calculate(); return swapBondDurations_; @@ -395,7 +395,7 @@ protected override void performCalculations() private Euribor euriborIndex_; private Handle discountCurve_; - private InitializedList yields_; + private List yields_; private List durations_; private double duration_; private int equivalentSwapIndex_; @@ -403,8 +403,8 @@ protected override void performCalculations() private int nSwaps_; private List swaps_; private List swapLenghts_; - private InitializedList swapBondDurations_; - private InitializedList swapBondYields_, swapRates_; + private List swapBondDurations_; + private List swapBondYields_, swapRates_; } //! RendistatoCalculator equivalent swap lenth Quote adapter diff --git a/QLNet/Instruments/CPICapFloor.cs b/QLNet/Instruments/CPICapFloor.cs new file mode 100644 index 000000000..ee49c5b1c --- /dev/null +++ b/QLNet/Instruments/CPICapFloor.cs @@ -0,0 +1,178 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + //! CPI cap or floor + /*! Quoted as a fixed strike rate \f$ K \f$. Payoff: + \f[ + P_n(0,T) \max(y (N [(1+K)^{T}-1] - + N \left[ \frac{I(T)}{I(0)} -1 \right]), 0) + \f] + where \f$ T \f$ is the maturity time, \f$ P_n(0,t) \f$ is the + nominal discount factor at time \f$ t \f$, \f$ N \f$ is the + notional, and \f$ I(t) \f$ is the inflation index value at + time \f$ t \f$. + + Inflation is generally available on every day, including + holidays and weekends. Hence there is a variable to state + whether the observe/fix dates for inflation are adjusted or + not. The default is not to adjust. + + N.B. a cpi cap or floor is an option, not a cap or floor on a coupon. + Thus this is very similar to a ZCIIS and has a single flow, this is + as usual for cpi because it is cumulative up to option maturity from base + date. + + We do not inherit from Option, although this would be reasonable, + because we do not have that degree of generality. + + */ + public class CPICapFloor : Instrument + { + public class Arguments : IPricingEngineArguments + { + public Option.Type type; + public double nominal; + public Date startDate, fixDate, payDate; + public double baseCPI; + public Date maturity; + public Calendar fixCalendar, payCalendar; + public BusinessDayConvention fixConvention, payConvention; + public double strike; + public Handle infIndex; + public Period observationLag; + public InterpolationType observationInterpolation; + + public void validate() {} + } + + public new class Results : Instrument.Results + { + public override void reset() { base.reset();} + } + + public class Engine : GenericEngine {} + + public CPICapFloor(Option.Type type, + double nominal, + Date startDate, // start date of contract (only) + double baseCPI, + Date maturity, // this is pre-adjustment! + Calendar fixCalendar, + BusinessDayConvention fixConvention, + Calendar payCalendar, + BusinessDayConvention payConvention, + double strike, + Handle infIndex, + Period observationLag, + InterpolationType observationInterpolation = InterpolationType.AsIndex) + { + type_ = type; + nominal_ = nominal; + startDate_ = startDate; + baseCPI_ = baseCPI; + maturity_ = maturity; + fixCalendar_ = fixCalendar; + fixConvention_ = fixConvention; + payCalendar_ = payCalendar; + payConvention_ = payConvention; + strike_ = strike; + infIndex_ = infIndex; + observationLag_ = observationLag; + observationInterpolation_ = observationInterpolation; + + Utils.QL_REQUIRE(fixCalendar_ != null, ()=> "CPICapFloor: fixing calendar may not be null."); + Utils.QL_REQUIRE(payCalendar_ != null, ()=> "CPICapFloor: payment calendar may not be null."); + + if (observationInterpolation_ == InterpolationType.Flat || + observationInterpolation_ == InterpolationType.AsIndex && !infIndex_.link.interpolated()) + { + Utils.QL_REQUIRE(observationLag_ >= infIndex_.link.availabilityLag(),()=> + "CPIcapfloor's observationLag must be at least availabilityLag of inflation index: " + +"when the observation is effectively flat" + + observationLag_ + " vs " + infIndex_.link.availabilityLag()); + } + if (observationInterpolation_ == InterpolationType.Linear || + (observationInterpolation_ == InterpolationType.AsIndex && infIndex_.link.interpolated())) + { + Utils.QL_REQUIRE(observationLag_ > infIndex_.link.availabilityLag(),()=> + "CPIcapfloor's observationLag must be greater then availabilityLag of inflation index: " + +"when the observation is effectively linear" + + observationLag_ + " vs " + infIndex_.link.availabilityLag()); + } + } + + //! \name Inspectors + //@{ + public Option.Type type() { return type_; } + public double nominal() { return nominal_; } + //! \f$ K \f$ in the above formula. + public double strike() { return strike_; } + //! when you fix - but remember that there is an observation interpolation factor as well + public Date fixingDate() { return fixCalendar_.adjust( maturity_ - observationLag_, fixConvention_ ); } + public Date payDate() { return payCalendar_.adjust( maturity_, payConvention_ ); } + public Handle inflationIndex() { return infIndex_; } + public Period observationLag() { return observationLag_; } + //@} + + //! \name Instrument interface + //@{ + public override bool isExpired() {return (Settings.evaluationDate() > maturity_);} + public override void setupArguments(IPricingEngineArguments args) + { + // correct PricingEngine? + CPICapFloor.Arguments arguments = args as CPICapFloor.Arguments; + Utils.QL_REQUIRE( arguments != null,()=> "wrong argument type, not CPICapFloor.Arguments" ); + + // data move + arguments.type = type_; + arguments.nominal = nominal_; + arguments.startDate = startDate_; + arguments.baseCPI = baseCPI_; + arguments.maturity = maturity_; + arguments.fixCalendar = fixCalendar_; + arguments.fixConvention = fixConvention_; + arguments.payCalendar = fixCalendar_; + arguments.payConvention = payConvention_; + arguments.fixDate = fixingDate(); + arguments.payDate = payDate(); + arguments.strike = strike_; + arguments.infIndex = infIndex_; + arguments.observationLag = observationLag_; + arguments.observationInterpolation = observationInterpolation_; + + } + + //@} + + protected Option.Type type_; + protected double nominal_; + protected Date startDate_, fixDate_, payDate_; + protected double baseCPI_; + protected Date maturity_; + protected Calendar fixCalendar_; + protected BusinessDayConvention fixConvention_; + protected Calendar payCalendar_; + protected BusinessDayConvention payConvention_; + protected double strike_; + protected Handle infIndex_; + protected Period observationLag_; + protected InterpolationType observationInterpolation_; + } + + +} diff --git a/QLNet/Instruments/CPISwap.cs b/QLNet/Instruments/CPISwap.cs new file mode 100644 index 000000000..57799821c --- /dev/null +++ b/QLNet/Instruments/CPISwap.cs @@ -0,0 +1,327 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +using System.Text; + +namespace QLNet +{ + //! zero-inflation-indexed swap, + /*! fixed x zero-inflation, i.e. fixed x CPI(i'th fixing)/CPI(base) + versus floating + spread + + Note that this does ony the inflation-vs-floating-leg. + Extension to inflation-vs-fixed-leg. is simple - just replace + the floating leg with a fixed leg. + + Typically there are notional exchanges at the end: either + inflated-notional vs notional; or just (inflated-notional - + notional) vs zero. The latter is perhaphs more typical. + \warning Setting subtractInflationNominal to true means that + the original inflation nominal is subtracted from both + nominals before they are exchanged, even if they are + different. + + This swap can mimic a ZCIIS where [(1+q)^n - 1] is exchanged + against (cpi ratio - 1), by using differnt nominals on each + leg and setting subtractInflationNominal to true. ALSO - + there must be just one date in each schedule. + + The two legs can have different schedules, fixing (days vs + lag), settlement, and roll conventions. N.B. accrual + adjustment periods are already in the schedules. Trade date + and swap settlement date are outside the scope of the + instrument. + */ + public class CPISwap : Swap + { + public enum Type { Receiver = -1, Payer = 1 }; + public new class Arguments : Swap.Arguments + { + public Arguments() + { + type = Type.Receiver; + nominal = null; + } + + public Type type; + public double? nominal; + + } + + public new class Results : Swap.Results + { + public double? fairRate; + public double? fairSpread; + public override void reset() + { + base.reset(); + fairRate = null; + fairSpread = null; + } + } + + public class Engine : GenericEngine + {} + + public CPISwap(Type type, + double nominal, + bool subtractInflationNominal, + // float+spread leg + double spread, + DayCounter floatDayCount, + Schedule floatSchedule, + BusinessDayConvention floatPaymentRoll, + int fixingDays, + IborIndex floatIndex, + // fixed x inflation leg + double fixedRate, + double baseCPI, + DayCounter fixedDayCount, + Schedule fixedSchedule, + BusinessDayConvention fixedPaymentRoll, + Period observationLag, + ZeroInflationIndex fixedIndex, + InterpolationType observationInterpolation = InterpolationType.AsIndex, + double? inflationNominal = null ) + :base(2) + { + type_ = type; + nominal_ = nominal; + subtractInflationNominal_ = subtractInflationNominal; + spread_ = spread; + floatDayCount_ = floatDayCount; + floatSchedule_ = floatSchedule; + floatPaymentRoll_ = floatPaymentRoll; + fixingDays_ = fixingDays; + floatIndex_ = floatIndex; + fixedRate_ = fixedRate; + baseCPI_ = baseCPI; + fixedDayCount_ = fixedDayCount; + fixedSchedule_ = fixedSchedule; + fixedPaymentRoll_ = fixedPaymentRoll; + fixedIndex_ = fixedIndex; + observationLag_ = observationLag; + observationInterpolation_ = observationInterpolation; + + Utils.QL_REQUIRE(floatSchedule_.Count>0,()=> "empty float schedule"); + Utils.QL_REQUIRE(fixedSchedule_.Count>0,()=> "empty fixed schedule"); + // todo if roll!=unadjusted then need calendars ... + + inflationNominal_ = inflationNominal ?? nominal_; + + List floatingLeg = null; + if (floatSchedule_.Count > 1) + { + floatingLeg = new IborLeg(floatSchedule_, floatIndex_) + .withFixingDays(fixingDays_) + .withPaymentDayCounter(floatDayCount_) + .withSpreads(spread_) + .withNotionals(nominal_) + .withPaymentAdjustment(floatPaymentRoll_); + } + + if (floatSchedule_.Count==1 || + !subtractInflationNominal_ || + (subtractInflationNominal && Math.Abs(nominal_-inflationNominal_)>0.00001) + ) + { + Date payNotional; + floatingLeg = new List(); + if (floatSchedule_.Count==1) + { + // no coupons + payNotional = floatSchedule_[0]; + payNotional = floatSchedule_.calendar().adjust(payNotional, floatPaymentRoll_); + } + else + { + // use the pay date of the last coupon + payNotional = floatingLeg.Last().date(); + } + + double floatAmount = subtractInflationNominal_ ? nominal_ - inflationNominal_ : nominal_; + CashFlow nf = new SimpleCashFlow(floatAmount, payNotional); + floatingLeg.Add(nf); + } + + // a CPIleg know about zero legs and inclusion of base inflation notional + List cpiLeg = new CPILeg(fixedSchedule_, fixedIndex_, baseCPI_, observationLag_) + .withFixedRates(fixedRate_) + .withPaymentDayCounter(fixedDayCount_) + .withObservationInterpolation(observationInterpolation_) + .withSubtractInflationNominal(subtractInflationNominal_) + .withNotionals(inflationNominal_) + .withPaymentAdjustment(fixedPaymentRoll_); + + foreach (CashFlow cashFlow in cpiLeg) + { + cashFlow.registerWith(update); + } + + foreach (CashFlow cashFlow in floatingLeg) + { + cashFlow.registerWith(update); + } + + + legs_[0] = cpiLeg; + legs_[1] = floatingLeg; + + if (type_==Type.Payer) + { + payer_[0] = 1.0; + payer_[1] = -1.0; + } + else + { + payer_[0] = -1.0; + payer_[1] = 1.0; + } + } + + // results + // float+spread + public virtual double floatLegNPV() + { + calculate(); + Utils.QL_REQUIRE(legNPV_[1] != null,()=> "result not available"); + return legNPV_[1].GetValueOrDefault(); + } + + public virtual double fairSpread() + { + calculate(); + Utils.QL_REQUIRE(fairSpread_ != null,()=> "result not available"); + return fairSpread_.GetValueOrDefault(); + } + // fixed rate x inflation + public virtual double fixedLegNPV() + { + calculate(); + Utils.QL_REQUIRE(legNPV_[0] != null,()=> "result not available"); + return legNPV_[0].GetValueOrDefault(); + } + public virtual double fairRate() + { + calculate(); + Utils.QL_REQUIRE(fairRate_ != null,()=> "result not available"); + return fairRate_.GetValueOrDefault(); + } + + // inspectors + public virtual Type type() {return type_; } + public virtual double nominal() {return nominal_;} + public virtual bool subtractInflationNominal() {return subtractInflationNominal_;} + + // float+spread + public virtual double spread() {return spread_; } + public virtual DayCounter floatDayCount() {return floatDayCount_;} + public virtual Schedule floatSchedule() {return floatSchedule_;} + public virtual BusinessDayConvention floatPaymentRoll() {return floatPaymentRoll_;} + public virtual int fixingDays() { return fixingDays_;} + public virtual IborIndex floatIndex() {return floatIndex_;} + + // fixed rate x inflation + public virtual double fixedRate() {return fixedRate_;} + public virtual double baseCPI() {return baseCPI_;} + public virtual DayCounter fixedDayCount() {return fixedDayCount_; } + public virtual Schedule fixedSchedule() {return fixedSchedule_; } + public virtual BusinessDayConvention fixedPaymentRoll() {return fixedPaymentRoll_;} + public virtual Period observationLag() {return observationLag_; } + public virtual ZeroInflationIndex fixedIndex() {return fixedIndex_;} + public virtual InterpolationType observationInterpolation() {return observationInterpolation_;} + public virtual double inflationNominal() {return inflationNominal_;} + + // legs + public virtual List cpiLeg() {return legs_[0];} + public virtual List floatLeg() {return legs_[1];} + + // other + public override void fetchResults(IPricingEngineResults r) + { + const double basisPoint = 1.0e-4; + + // copy from VanillaSwap + // works because similarly simple instrument + // that we always expect to be priced with a swap engine + + base.fetchResults(r); + + CPISwap.Results results = r as CPISwap.Results; + + if (results!=null) + { + // might be a swap engine, so no error is thrown + fairRate_ = results.fairRate; + fairSpread_ = results.fairSpread; + } + else + { + fairRate_ = null; + fairSpread_ = null; + } + + if (fairRate_ == null) + { + // calculate it from other results + if (legBPS_[0] != null) + fairRate_ = fixedRate_ - NPV_/(legBPS_[0]/basisPoint); + } + if (fairSpread_ == null) + { + // ditto + if (legBPS_[1] != null) + fairSpread_ = spread_ - NPV_/(legBPS_[1]/basisPoint); + } + } + + protected override void setupExpired() + { + base.setupExpired(); + legBPS_[0] = legBPS_[1] = 0.0; + fairRate_ = null; + fairSpread_ = null; + } + + private Type type_; + private double nominal_; + private bool subtractInflationNominal_; + + // float+spread leg + private double spread_; + private DayCounter floatDayCount_; + private Schedule floatSchedule_; + private BusinessDayConvention floatPaymentRoll_; + private int fixingDays_; + private IborIndex floatIndex_; + + // fixed x inflation leg + private double fixedRate_; + private double baseCPI_; + private DayCounter fixedDayCount_; + private Schedule fixedSchedule_; + private BusinessDayConvention fixedPaymentRoll_; + private ZeroInflationIndex fixedIndex_; + private Period observationLag_; + private InterpolationType observationInterpolation_; + private double inflationNominal_; + // results + private double? fairSpread_; + private double? fairRate_; + } +} diff --git a/QLNet/Instruments/CliquetOption.cs b/QLNet/Instruments/CliquetOption.cs new file mode 100644 index 000000000..c7fdb27d1 --- /dev/null +++ b/QLNet/Instruments/CliquetOption.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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.Collections.Generic; + +namespace QLNet +{ + //! cliquet (Ratchet) option + /*! A cliquet option, also known as Ratchet option, is a series of + forward-starting (a.k.a. deferred strike) options where the + strike for each forward start option is set equal to a fixed + percentage of the spot price at the beginning of each period. + + \todo + - add local/global caps/floors + - add accrued coupon and last fixing + + \ingroup instruments + */ + public class CliquetOption : OneAssetOption + { + public CliquetOption( PercentageStrikePayoff payoff, EuropeanExercise maturity,List resetDates) + :base(payoff,maturity) + { + resetDates_ = new List(resetDates); + } + public override void setupArguments(IPricingEngineArguments args) + { + base.setupArguments(args); + // set accrued coupon, last fixing, caps, floors + CliquetOption.Arguments moreArgs = args as CliquetOption.Arguments; + Utils.QL_REQUIRE(moreArgs != null,()=> "wrong engine type"); + moreArgs.resetDates = new List(resetDates_); + } + private List resetDates_; + + //! %Arguments for cliquet option calculation + // should inherit from a strikeless version of VanillaOption::arguments + public new class Arguments : OneAssetOption.Arguments + { + public Arguments() + { + accruedCoupon = null; + lastFixing = null; + localCap = null; + localFloor = null; + globalCap = null; + globalFloor = null; + } + public override void validate() + { + PercentageStrikePayoff moneyness = payoff as PercentageStrikePayoff; + Utils.QL_REQUIRE(moneyness != null ,()=> "wrong payoff type"); + Utils.QL_REQUIRE(moneyness.strike() > 0.0,()=> "negative or zero moneyness given"); + Utils.QL_REQUIRE(accruedCoupon == null || accruedCoupon >= 0.0,()=> "negative accrued coupon"); + Utils.QL_REQUIRE(localCap == null || localCap >= 0.0,()=> "negative local cap"); + Utils.QL_REQUIRE(localFloor == null || localFloor >= 0.0,()=> "negative local floor"); + Utils.QL_REQUIRE(globalCap == null || globalCap >= 0.0,()=> "negative global cap"); + Utils.QL_REQUIRE(globalFloor == null || globalFloor >= 0.0,()=> "negative global floor"); + Utils.QL_REQUIRE(!resetDates.empty(),()=> "no reset dates given"); + for ( int i = 0; i < resetDates.Count; ++i ) + { + Utils.QL_REQUIRE( exercise.lastDate() > resetDates[i], () => "reset date greater or equal to maturity" ); + Utils.QL_REQUIRE( i == 0 || resetDates[i] > resetDates[i - 1], () => "unsorted reset dates" ); + } + } + public double? accruedCoupon, lastFixing; + public double? localCap, localFloor, globalCap, globalFloor; + public List resetDates; + } + + //! Cliquet %engine base class + public new class Engine : GenericEngine + {} + } + +} diff --git a/QLNet/Instruments/CompositeInstrument.cs b/QLNet/Instruments/CompositeInstrument.cs new file mode 100644 index 000000000..f3dcadde9 --- /dev/null +++ b/QLNet/Instruments/CompositeInstrument.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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.Collections.Generic; +using component= System.Collections.Generic.KeyValuePair; + +namespace QLNet +{ + //! %Composite instrument + /*! This instrument is an aggregate of other instruments. Its NPV + is the sum of the NPVs of its components, each possibly + multiplied by a given factor. + + \warning Methods that drive the calculation directly (such as + recalculate(), freeze() and others) might not work + correctly. + + \ingroup instruments + */ + public class CompositeInstrument : Instrument + { + //! adds an instrument to the composite + public void add(Instrument instrument,double multiplier = 1.0) + { + components_.Add(new KeyValuePair(instrument,multiplier)); + instrument.registerWith(update); + update(); + } + + //! shorts an instrument from the composite + public void subtract(Instrument instrument,double multiplier = 1.0) + { + add( instrument, -multiplier ); + } + //! \name Instrument interface + //@{ + public override bool isExpired() + { + foreach (component c in components_) + { + if ( !c.Key.isExpired() ) + return false; + } + return true; + } + + protected override void performCalculations() + { + NPV_ = 0.0; + foreach (component c in components_) + { + NPV_ += c.Value * c.Key.NPV(); + } + } + //@} + private List components_ = new List(); + + } +} diff --git a/QLNet/Instruments/DividendBarrierOption.cs b/QLNet/Instruments/DividendBarrierOption.cs new file mode 100644 index 000000000..f0c552f30 --- /dev/null +++ b/QLNet/Instruments/DividendBarrierOption.cs @@ -0,0 +1,75 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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.Collections.Generic; + +namespace QLNet +{ + //! Single-asset barrier option with discrete dividends + /*! \ingroup instruments */ + public class DividendBarrierOption : BarrierOption + { + public DividendBarrierOption( Barrier.Type barrierType, + double barrier, + double rebate, + StrikedTypePayoff payoff, + Exercise exercise, + List dividendDates, + List dividends) + :base(barrierType, barrier, rebate, payoff, exercise) + { + cashFlow_ = Utils.DividendVector( dividendDates, dividends ); + } + + public override void setupArguments( IPricingEngineArguments args ) + { + base.setupArguments(args); + + DividendBarrierOption.Arguments arguments = args as DividendBarrierOption.Arguments; + Utils.QL_REQUIRE(arguments != null,()=> "wrong engine type"); + + arguments.cashFlow = cashFlow_; + } + + private List cashFlow_; + + + //! %Arguments for dividend barrier option calculation + public new class Arguments : BarrierOption.Arguments + { + public List cashFlow = new List(); + public Arguments() {} + public override void validate() + { + base.validate(); + + Date exerciseDate = exercise.lastDate(); + + for (int i = 0; i < cashFlow.Count; i++) + { + Utils.QL_REQUIRE(cashFlow[i].date() <= exerciseDate,()=> + "the " + (i+1) + " dividend date ("+ cashFlow[i].date()+ ") is later than the exercise date (" + + exerciseDate + ")"); + } + } + } + //! %Dividend-barrier-option %engine base class + public new class Engine : GenericEngine {} + + } + +} + diff --git a/QLNet/Instruments/DividendVanillaOption.cs b/QLNet/Instruments/DividendVanillaOption.cs index 6d1ec2cf5..b5e250fad 100644 --- a/QLNet/Instruments/DividendVanillaOption.cs +++ b/QLNet/Instruments/DividendVanillaOption.cs @@ -78,7 +78,7 @@ public override void setupArguments(IPricingEngineArguments args) { //! %Arguments for dividend vanilla option calculation - new public class Arguments : OneAssetOption.Arguments { + public new class Arguments : OneAssetOption.Arguments { public List cashFlow; public override void validate() { @@ -95,6 +95,6 @@ public override void validate() { } //! %Dividend-vanilla-option %engine base class - new public class Engine : GenericEngine { } + public new class Engine : GenericEngine { } } } diff --git a/QLNet/Instruments/DoubleBarrierOption.cs b/QLNet/Instruments/DoubleBarrierOption.cs new file mode 100644 index 000000000..955ecd818 --- /dev/null +++ b/QLNet/Instruments/DoubleBarrierOption.cs @@ -0,0 +1,146 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + public struct DoubleBarrier + { + public enum Type + { + KnockIn, + KnockOut, + KIKO, //! lower barrier KI, upper KO + KOKI //! lower barrier KO, upper KI + } + } + + //! %Double Barrier option on a single asset. + /*! The analytic pricing engine will be used if none if passed. + + \ingroup instruments + */ + public class DoubleBarrierOption : OneAssetOption + { + + public DoubleBarrierOption(DoubleBarrier.Type barrierType, + double barrier_lo, + double barrier_hi, + double rebate, + StrikedTypePayoff payoff, + Exercise exercise) + :base(payoff, exercise) + { + barrierType_ = barrierType; + barrier_lo_ = barrier_lo; + barrier_hi_ = barrier_hi; + rebate_ = rebate; + } + + public override void setupArguments(IPricingEngineArguments args) + { + base.setupArguments(args); + + DoubleBarrierOption.Arguments moreArgs = args as DoubleBarrierOption.Arguments; + Utils.QL_REQUIRE(moreArgs != null,()=> "wrong argument type"); + moreArgs.barrierType = barrierType_; + moreArgs.barrier_lo = barrier_lo_; + moreArgs.barrier_hi = barrier_hi_; + moreArgs.rebate = rebate_; + } + + /*! \warning see VanillaOption for notes on implied-volatility + calculation. + */ + public double impliedVolatility( double targetValue, + GeneralizedBlackScholesProcess process, + double accuracy = 1.0e-4, + int maxEvaluations = 100, + double minVol = 1.0e-7, + double maxVol = 4.0) + { + Utils.QL_REQUIRE(!isExpired(),()=> "option expired"); + + SimpleQuote volQuote=new SimpleQuote(); + + GeneralizedBlackScholesProcess newProcess = ImpliedVolatilityHelper.clone(process, volQuote); + + // engines are built-in for the time being + IPricingEngine engine = null; + + switch (exercise_.type()) + { + case Exercise.Type.European: + engine = new AnalyticDoubleBarrierEngine(newProcess); + break; + case Exercise.Type.American: + case Exercise.Type.Bermudan: + Utils.QL_FAIL("engine not available for non-European barrier option"); + break; + default: + Utils.QL_FAIL("unknown exercise type"); + break; + } + + return ImpliedVolatilityHelper.calculate(this,engine,volQuote,targetValue,accuracy,maxEvaluations,minVol, maxVol); + + } + + // arguments + protected DoubleBarrier.Type barrierType_; + protected double barrier_lo_; + protected double barrier_hi_; + protected double rebate_; + + //! %Arguments for double barrier option calculation + public new class Arguments : OneAssetOption.Arguments + { + public Arguments() + { + barrier_lo = null; + barrier_hi = null; + rebate = null; + } + public DoubleBarrier.Type barrierType; + public double? barrier_lo; + public double? barrier_hi; + public double? rebate; + public override void validate() + { + base.validate(); + + Utils.QL_REQUIRE(barrierType == DoubleBarrier.Type.KnockIn || + barrierType == DoubleBarrier.Type.KnockOut || + barrierType == DoubleBarrier.Type.KIKO || + barrierType == DoubleBarrier.Type.KOKI,()=> + "Invalid barrier type"); + + Utils.QL_REQUIRE(barrier_lo != null,()=> "no low barrier given"); + Utils.QL_REQUIRE(barrier_hi !=null,()=> "no high barrier given"); + Utils.QL_REQUIRE(rebate != null,()=> "no rebate given"); + } + } + + //! %Double-Barrier-option %engine base class + public new class Engine : GenericEngine + { + protected bool triggered(double underlying) + { + return underlying <= arguments_.barrier_lo || underlying >= arguments_.barrier_hi; + } + } + + } +} diff --git a/QLNet/Instruments/ForwardVanillaOption.cs b/QLNet/Instruments/ForwardVanillaOption.cs new file mode 100644 index 000000000..0874726a2 --- /dev/null +++ b/QLNet/Instruments/ForwardVanillaOption.cs @@ -0,0 +1,73 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + public class ForwardVanillaOption : OneAssetOption + { + public ForwardVanillaOption( double moneyness, + Date resetDate, + StrikedTypePayoff payoff, + Exercise exercise) + :base(payoff, exercise) + { + moneyness_ = moneyness; + resetDate_ = resetDate; + } + + public override void setupArguments(IPricingEngineArguments args) + { + base.setupArguments(args); + ForwardVanillaOption.Arguments arguments = args as ForwardVanillaOption.Arguments; + Utils.QL_REQUIRE(arguments != null,()=> "wrong argument type"); + + arguments.moneyness = moneyness_; + arguments.resetDate = resetDate_; + } + public override void fetchResults(IPricingEngineResults r) + { + base.fetchResults(r); + ForwardVanillaOption.Results results = r as ForwardVanillaOption.Results; + Utils.QL_REQUIRE(results != null,()=> "no results returned from pricing engine"); + delta_ = results.delta; + gamma_ = results.gamma; + theta_ = results.theta; + vega_ = results.vega; + rho_ = results.rho; + dividendRho_ = results.dividendRho; + } + + // arguments + private double moneyness_; + private Date resetDate_; + + public new class Arguments : OneAssetOption.Arguments + { + public override void validate() + { + //Utils.QL_REQUIRE(moneyness != null,()=> "null moneyness given"); + Utils.QL_REQUIRE(moneyness > 0.0,()=> "negative or zero moneyness given"); + + Utils.QL_REQUIRE(resetDate != null,()=> "null reset date given"); + Utils.QL_REQUIRE(resetDate >= Settings.evaluationDate(),()=>"reset date in the past"); + Utils.QL_REQUIRE(this.exercise.lastDate() > resetDate,()=>"reset date later or equal to maturity"); + } + public double moneyness; + public Date resetDate; + } + + } +} diff --git a/QLNet/Instruments/Instrument.cs b/QLNet/Instruments/Instrument.cs index 39295de99..3505cfa3b 100644 --- a/QLNet/Instruments/Instrument.cs +++ b/QLNet/Instruments/Instrument.cs @@ -90,7 +90,7 @@ public virtual void fetchResults(IPricingEngineResults r) CASH_ = results.cash; errorEstimate_ = results.errorEstimate; valuationDate_ = results.valuationDate; - additionalResults_ = results.additionalResults; + additionalResults_ = new Dictionary(results.additionalResults); } public double NPV() diff --git a/QLNet/Instruments/Loan.cs b/QLNet/Instruments/Loan.cs index 26c0c2a7e..949acf33f 100644 --- a/QLNet/Instruments/Loan.cs +++ b/QLNet/Instruments/Loan.cs @@ -32,10 +32,10 @@ public enum Amortising Step = 2, French = 3 } - protected InitializedList> legs_; - protected InitializedList payer_; + protected List> legs_; + protected List payer_; protected List notionals_; - protected InitializedList legNPV_; + protected List legNPV_; public Loan(int legs) { @@ -79,7 +79,7 @@ public override void fetchResults(IPricingEngineResults r) { if (results.legNPV.Count != legNPV_.Count) throw new ArgumentException("wrong number of leg NPV returned"); - legNPV_ = results.legNPV; + legNPV_ = new List(results.legNPV); } else { @@ -102,12 +102,15 @@ public virtual void validate() public new class Results : Instrument.Results { - public InitializedList legNPV = new InitializedList(); + public List legNPV ; public override void reset() { base.reset(); // clear all previous results - legNPV.Erase(); + if (legNPV==null) + legNPV = new List(); + else + legNPV.Clear(); } } diff --git a/QLNet/Instruments/LookbackOption.cs b/QLNet/Instruments/LookbackOption.cs new file mode 100644 index 000000000..e06f27c3e --- /dev/null +++ b/QLNet/Instruments/LookbackOption.cs @@ -0,0 +1,223 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + //! Continuous-floating lookback option + public class ContinuousFloatingLookbackOption : OneAssetOption + { + //! %Arguments for continuous fixed lookback option calculation + public new class Arguments : OneAssetOption.Arguments + { + public double? minmax; + public override void validate() + { + base.validate(); + + Utils.QL_REQUIRE(minmax != null,()=> "null prior extremum"); + Utils.QL_REQUIRE(minmax >= 0.0,()=> "nonnegative prior extremum required: " + minmax + " not allowed"); + } + } + + //! %Continuous floating lookback %engine base class + public new class Engine : GenericEngine + {} + public ContinuousFloatingLookbackOption( double minmax, TypePayoff payoff, Exercise exercise ) + :base(payoff, exercise) + { + minmax_ = minmax; + } + + public override void setupArguments(IPricingEngineArguments args ) + { + base.setupArguments(args); + + ContinuousFloatingLookbackOption.Arguments moreArgs = args as ContinuousFloatingLookbackOption.Arguments; + Utils.QL_REQUIRE(moreArgs != null,()=> "wrong argument type"); + moreArgs.minmax = minmax_; + } + + // arguments + protected double? minmax_; + } + + //! Continuous-fixed lookback option + public class ContinuousFixedLookbackOption : OneAssetOption + { + //! %Arguments for continuous fixed lookback option calculation + public new class Arguments : OneAssetOption.Arguments + { + public double? minmax; + public override void validate() + { + base.validate(); + + Utils.QL_REQUIRE(minmax != null,()=> "null prior extremum"); + Utils.QL_REQUIRE(minmax >= 0.0,()=> "nonnegative prior extremum required: " + + minmax + " not allowed"); + } + } + + //! %Continuous fixed lookback %engine base class + public new class Engine : GenericEngine + {} + public ContinuousFixedLookbackOption( double minmax, StrikedTypePayoff payoff, Exercise exercise ) + :base(payoff, exercise) + { + minmax_ = minmax; + } + public override void setupArguments(IPricingEngineArguments args) + { + base.setupArguments(args); + + ContinuousFixedLookbackOption.Arguments moreArgs = args as ContinuousFixedLookbackOption.Arguments; + Utils.QL_REQUIRE(moreArgs != null,()=> "wrong argument type"); + moreArgs.minmax = minmax_; + } + + protected double minmax_; + } + + //! Continuous-partial-floating lookback option + /*! From http://help.rmetrics.org/fExoticOptions/LookbackOptions.html : + + For a partial-time floating strike lookback option, the + lookback period starts at time zero and ends at an arbitrary + date before expiration. Except for the partial lookback + period, the option is similar to a floating strike lookback + option. The partial-time floating strike lookback option is + cheaper than a similar standard floating strike lookback + option. Partial-time floating strike lookback options can be + priced analytically using a model introduced by Heynen and Kat + (1994). + + */ + public class ContinuousPartialFloatingLookbackOption : ContinuousFloatingLookbackOption + { + //! %Arguments for continuous partial floating lookback option calculation + public new class Arguments: ContinuousFloatingLookbackOption.Arguments + { + public double lambda; + public Date lookbackPeriodEnd; + public override void validate() + { + base.validate(); + + EuropeanExercise europeanExercise = exercise as EuropeanExercise; + Utils.QL_REQUIRE(lookbackPeriodEnd <= europeanExercise.lastDate(), ()=> + "lookback start date must be earlier than exercise date"); + + FloatingTypePayoff floatingTypePayoff = payoff as FloatingTypePayoff; + + if (floatingTypePayoff.optionType() == Option.Type.Call) + { + Utils.QL_REQUIRE(lambda >= 1.0,()=> + "lambda should be greater than or equal to 1 for calls"); + } + + if (floatingTypePayoff.optionType() == Option.Type.Put) + { + Utils.QL_REQUIRE(lambda <= 1.0,()=> + "lambda should be smaller than or equal to 1 for puts"); + } + } + } + + //! %Continuous partial floating lookback %engine base class + public new class Engine: GenericEngine + {} + + public ContinuousPartialFloatingLookbackOption( double minmax, double lambda, + Date lookbackPeriodEnd,TypePayoff payoff,Exercise exercise) + :base(minmax, payoff, exercise) + { + lambda_ = lambda; + lookbackPeriodEnd_ = lookbackPeriodEnd; + } + public override void setupArguments(IPricingEngineArguments args) + { + base.setupArguments(args); + + ContinuousPartialFloatingLookbackOption.Arguments moreArgs = args as ContinuousPartialFloatingLookbackOption.Arguments; + Utils.QL_REQUIRE(moreArgs != null,()=> "wrong argument type"); + moreArgs.lambda = lambda_; + moreArgs.lookbackPeriodEnd = lookbackPeriodEnd_; + } + + protected double lambda_; + protected Date lookbackPeriodEnd_; + } + + + //! Continuous-partial-fixed lookback option + /*! From http://help.rmetrics.org/fExoticOptions/LookbackOptions.html : + + For a partial-time fixed strike lookback option, the lookback + period starts at a predetermined date after the initialization + date of the option. The partial-time fixed strike lookback + call option payoff is given by the difference between the + maximum observed price of the underlying asset during the + lookback period and the fixed strike price. The partial-time + fixed strike lookback put option payoff is given by the + difference between the fixed strike price and the minimum + observed price of the underlying asset during the lookback + period. The partial-time fixed strike lookback option is + cheaper than a similar standard fixed strike lookback + option. Partial-time fixed strike lookback options can be + priced analytically using a model introduced by Heynen and Kat + (1994). + + */ + public class ContinuousPartialFixedLookbackOption : ContinuousFixedLookbackOption + { + //! %Arguments for continuous partial fixed lookback option calculation + public new class Arguments : ContinuousFixedLookbackOption.Arguments + { + public Date lookbackPeriodStart; + public override void validate() + { + base.validate(); + + EuropeanExercise europeanExercise = exercise as EuropeanExercise; + Utils.QL_REQUIRE(lookbackPeriodStart <= europeanExercise.lastDate(), ()=> + "lookback start date must be earlier than exercise date"); + } + } + //! %Continuous partial fixed lookback %engine base class + public new class Engine : GenericEngine + {} + public ContinuousPartialFixedLookbackOption(Date lookbackPeriodStart,StrikedTypePayoff payoff,Exercise exercise) + :base(0, payoff, exercise) + { + lookbackPeriodStart_ = lookbackPeriodStart; + } + public override void setupArguments(IPricingEngineArguments args) + { + base.setupArguments(args); + + ContinuousPartialFixedLookbackOption.Arguments moreArgs = args as ContinuousPartialFixedLookbackOption.Arguments; + Utils.QL_REQUIRE(moreArgs != null,()=> "wrong argument type"); + moreArgs.lookbackPeriodStart = lookbackPeriodStart_; + } + + protected Date lookbackPeriodStart_; + } + +} diff --git a/QLNet/Instruments/MakeCms.cs b/QLNet/Instruments/MakeCms.cs new file mode 100644 index 000000000..7d223368f --- /dev/null +++ b/QLNet/Instruments/MakeCms.cs @@ -0,0 +1,348 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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.Collections.Generic; + +namespace QLNet +{ + //! helper class for instantiating CMS + /*! This class provides a more comfortable way + to instantiate standard market constant maturity swap. + */ + public class MakeCms + { + public MakeCms( Period swapTenor, + SwapIndex swapIndex, + IborIndex iborIndex, + double iborSpread = 0.0, + Period forwardStart = null) + { + swapTenor_ = swapTenor; + swapIndex_ = swapIndex; + iborIndex_ = iborIndex; + iborSpread_ = iborSpread; + useAtmSpread_ = false; + forwardStart_ = forwardStart ?? new Period(0,TimeUnit.Days); + cmsSpread_ = 0.0; + cmsGearing_ = 1.0; + cmsCap_ = 0; + cmsFloor_ = 0; + effectiveDate_ = null; + cmsCalendar_ = swapIndex.fixingCalendar(); + floatCalendar_ = iborIndex.fixingCalendar(); + payCms_ = true; nominal_ = 1.0; + cmsTenor_ = new Period(3,TimeUnit.Months); + floatTenor_ = iborIndex.tenor(); + cmsConvention_ = BusinessDayConvention.ModifiedFollowing; + cmsTerminationDateConvention_ = BusinessDayConvention.ModifiedFollowing; + floatConvention_ = iborIndex.businessDayConvention(); + floatTerminationDateConvention_ = iborIndex.businessDayConvention(); + cmsRule_ = DateGeneration.Rule.Backward; + floatRule_ = DateGeneration.Rule.Backward; + cmsEndOfMonth_ = false; + floatEndOfMonth_ = false; + cmsFirstDate_ = null; + cmsNextToLastDate_ = null; + floatFirstDate_ = null; + floatNextToLastDate_ = null; + cmsDayCount_ = new Actual360(); + floatDayCount_ = iborIndex.dayCounter(); + // arbitrary choice: + //engine_ = new DiscountingSwapEngine(iborIndex->termStructure()); + engine_ = new DiscountingSwapEngine(swapIndex.forwardingTermStructure()); + } + + public MakeCms( Period swapTenor, + SwapIndex swapIndex, + double iborSpread = 0.0, + Period forwardStart = null) + { + swapTenor_ = swapTenor; + swapIndex_ = swapIndex; + iborIndex_ = swapIndex.iborIndex(); + iborSpread_ = iborSpread; + useAtmSpread_ = false; + forwardStart_ = forwardStart ?? new Period(0,TimeUnit.Days); + cmsSpread_ = 0.0; + cmsGearing_ = 1.0; + cmsCap_ = 0; + cmsFloor_ = 0; + effectiveDate_ = null; + cmsCalendar_ = swapIndex.fixingCalendar(); + floatCalendar_ = iborIndex_.fixingCalendar(); + payCms_ = true; + nominal_ = 1.0; + cmsTenor_ = new Period(3,TimeUnit.Months); + floatTenor_ = iborIndex_.tenor(); + cmsConvention_ = BusinessDayConvention.ModifiedFollowing; + cmsTerminationDateConvention_ = BusinessDayConvention.ModifiedFollowing; + floatConvention_ = iborIndex_.businessDayConvention(); + floatTerminationDateConvention_ = iborIndex_.businessDayConvention(); + cmsRule_ = DateGeneration.Rule.Backward; + floatRule_ = DateGeneration.Rule.Backward; + cmsEndOfMonth_ = false; + floatEndOfMonth_ = false; + cmsFirstDate_ = null; + cmsNextToLastDate_ = null; + floatFirstDate_ = null; + floatNextToLastDate_ = null; + cmsDayCount_ = new Actual360(); + floatDayCount_ = iborIndex_.dayCounter(); + engine_ = new DiscountingSwapEngine(swapIndex.forwardingTermStructure()); + } + + public Swap value() + { + Date startDate; + if (effectiveDate_ != null) + startDate = effectiveDate_; + else + { + int fixingDays = iborIndex_.fixingDays(); + Date refDate = Settings.evaluationDate(); + // if the evaluation date is not a business day + // then move to the next business day + refDate = floatCalendar_.adjust(refDate); + Date spotDate = floatCalendar_.advance(refDate, new Period(fixingDays,TimeUnit.Days)); + startDate = spotDate+forwardStart_; + } + + Date terminationDate = startDate+swapTenor_; + + Schedule cmsSchedule = new Schedule(startDate, terminationDate, + cmsTenor_, cmsCalendar_, + cmsConvention_, + cmsTerminationDateConvention_, + cmsRule_, cmsEndOfMonth_, + cmsFirstDate_, cmsNextToLastDate_); + + Schedule floatSchedule = new Schedule(startDate, terminationDate, + floatTenor_, floatCalendar_, + floatConvention_, + floatTerminationDateConvention_, + floatRule_ , floatEndOfMonth_, + floatFirstDate_, floatNextToLastDate_); + + List cmsLeg = new CmsLeg(cmsSchedule, swapIndex_) + .withPaymentDayCounter(cmsDayCount_) + .withFixingDays(swapIndex_.fixingDays()) + .withGearings(cmsGearing_) + .withSpreads(cmsSpread_) + .withCaps(cmsCap_) + .withFloors(cmsFloor_) + .withNotionals(nominal_) + .withPaymentAdjustment(cmsConvention_); + + if (couponPricer_ != null) + Utils.setCouponPricer(cmsLeg, couponPricer_); + + double? usedSpread = iborSpread_; + if (useAtmSpread_) + { + Utils.QL_REQUIRE(!iborIndex_.forwardingTermStructure().empty(),()=> + "null term structure set to this instance of " + iborIndex_.name()); + Utils.QL_REQUIRE(!swapIndex_.forwardingTermStructure().empty(),()=> + "null term structure set to this instance of " + swapIndex_.name()); + Utils.QL_REQUIRE(couponPricer_ != null,()=> "no CmsCouponPricer set (yet)"); + List fLeg = new IborLeg(floatSchedule, iborIndex_) + .withPaymentDayCounter(floatDayCount_) + .withFixingDays(iborIndex_.fixingDays()) + .withNotionals(nominal_) + .withPaymentAdjustment(floatConvention_); + + Swap temp = new Swap(cmsLeg, fLeg); + temp.setPricingEngine(engine_); + + double? npv = temp.legNPV(0)+temp.legNPV(1); + + usedSpread = -npv/temp.legBPS(1)*1e-4; + } + else + { + Utils.QL_REQUIRE(usedSpread.HasValue,()=>"null spread set"); + } + + List floatLeg = new IborLeg(floatSchedule, iborIndex_) + .withSpreads(usedSpread.Value) + .withPaymentDayCounter(floatDayCount_) + .withFixingDays(iborIndex_.fixingDays()) + .withPaymentAdjustment(floatConvention_) + .withNotionals(nominal_); + + Swap swap; + if (payCms_) + swap = new Swap(cmsLeg, floatLeg); + else + swap = new Swap(floatLeg, cmsLeg); + swap.setPricingEngine(engine_); + return swap; + } + + public MakeCms receiveCms(bool flag = true) + { + payCms_ = !flag; + return this; + } + public MakeCms withNominal(double n) + { + nominal_ = n; + return this; + } + public MakeCms withEffectiveDate( Date effectiveDate ) + { + effectiveDate_ = effectiveDate; + return this; + } + + public MakeCms withCmsLegTenor(Period t) + { + cmsTenor_ = t; + return this; + } + public MakeCms withCmsLegCalendar( Calendar cal) + { + cmsCalendar_ = cal; + return this; + } + public MakeCms withCmsLegConvention(BusinessDayConvention bdc) + { + cmsConvention_ = bdc; + return this; + } + public MakeCms withCmsLegTerminationDateConvention( BusinessDayConvention bdc ) + { + cmsTerminationDateConvention_ = bdc; + return this; + } + public MakeCms withCmsLegRule(DateGeneration.Rule r) + { + cmsRule_ = r; + return this; + } + public MakeCms withCmsLegEndOfMonth(bool flag = true) + { + cmsEndOfMonth_ = flag; + return this; + } + public MakeCms withCmsLegFirstDate( Date d) + { + cmsFirstDate_ = d; + return this; + } + public MakeCms withCmsLegNextToLastDate( Date d) + { + cmsNextToLastDate_ = d; + return this; + } + public MakeCms withCmsLegDayCount( DayCounter dc) + { + cmsDayCount_ = dc; + return this; + } + + public MakeCms withFloatingLegTenor( Period t) + { + floatTenor_ = t; + return this; + } + public MakeCms withFloatingLegCalendar( Calendar cal) + { + floatCalendar_ = cal; + return this; + } + public MakeCms withFloatingLegConvention(BusinessDayConvention bdc) + { + floatConvention_ = bdc; + return this; + } + public MakeCms withFloatingLegTerminationDateConvention( BusinessDayConvention bdc) + { + floatTerminationDateConvention_ = bdc; + return this; + } + public MakeCms withFloatingLegRule(DateGeneration.Rule r) + { + floatRule_ = r; + return this; + } + public MakeCms withFloatingLegEndOfMonth(bool flag = true) + { + floatEndOfMonth_ = flag; + return this; + } + public MakeCms withFloatingLegFirstDate( Date d) + { + floatFirstDate_ = d; + return this; + } + public MakeCms withFloatingLegNextToLastDate( Date d) + { + floatNextToLastDate_ = d; + return this; + } + public MakeCms withFloatingLegDayCount( DayCounter dc) + { + floatDayCount_ = dc; + return this; + } + + public MakeCms withAtmSpread(bool flag = true) + { + useAtmSpread_ = flag; + return this; + } + + public MakeCms withDiscountingTermStructure( Handle discountingTermStructure) + { + engine_ = new DiscountingSwapEngine(discountingTermStructure); + return this; + } + public MakeCms withCmsCouponPricer( CmsCouponPricer couponPricer) + { + couponPricer_ = couponPricer; + return this; + } + + private Period swapTenor_; + private SwapIndex swapIndex_; + private IborIndex iborIndex_; + private double iborSpread_; + private bool useAtmSpread_; + private Period forwardStart_; + + private double cmsSpread_; + private double cmsGearing_; + private double cmsCap_, cmsFloor_; + + private Date effectiveDate_; + private Calendar cmsCalendar_, floatCalendar_; + + private bool payCms_; + private double nominal_; + private Period cmsTenor_, floatTenor_; + private BusinessDayConvention cmsConvention_, cmsTerminationDateConvention_; + private BusinessDayConvention floatConvention_, floatTerminationDateConvention_; + private DateGeneration.Rule cmsRule_, floatRule_; + private bool cmsEndOfMonth_, floatEndOfMonth_; + private Date cmsFirstDate_, cmsNextToLastDate_; + private Date floatFirstDate_, floatNextToLastDate_; + private DayCounter cmsDayCount_, floatDayCount_; + + private IPricingEngine engine_; + private CmsCouponPricer couponPricer_; + + } +} diff --git a/QLNet/Instruments/MultiAssetOption.cs b/QLNet/Instruments/MultiAssetOption.cs index 2cc43e90a..79c9ea24e 100644 --- a/QLNet/Instruments/MultiAssetOption.cs +++ b/QLNet/Instruments/MultiAssetOption.cs @@ -25,7 +25,7 @@ public class MultiAssetOption : Option { public class Engine : GenericEngine {}; - new public class Results : Instrument.Results + public new class Results : Instrument.Results { public double? delta, gamma, theta, vega, rho, dividendRho; diff --git a/QLNet/Instruments/OneAssetOption.cs b/QLNet/Instruments/OneAssetOption.cs index b34e4093c..b3a398109 100644 --- a/QLNet/Instruments/OneAssetOption.cs +++ b/QLNet/Instruments/OneAssetOption.cs @@ -141,7 +141,7 @@ results are returned (throw? numerical calculation?) //! %Results from single-asset option calculation - new public class Results : Instrument.Results { + public new class Results : Instrument.Results { public double? delta, gamma, theta, vega, rho, dividendRho; public double? itmCashProbability, deltaForward, elasticity, thetaPerDay, strikeSensitivity; diff --git a/QLNet/Instruments/Swap.cs b/QLNet/Instruments/Swap.cs index 7dcbeb0b7..3fdd04b8a 100644 --- a/QLNet/Instruments/Swap.cs +++ b/QLNet/Instruments/Swap.cs @@ -29,12 +29,12 @@ public class Swap : Instrument { #region Data members - protected InitializedList> legs_; - protected InitializedList payer_; - protected InitializedList legNPV_; - protected InitializedList legBPS_; - protected InitializedList startDiscounts_; - protected InitializedList endDiscounts_; + protected List> legs_; + protected List payer_; + protected List legNPV_; + protected List legBPS_; + protected List startDiscounts_; + protected List endDiscounts_; protected double? npvDateDiscount_; public Arguments arguments; @@ -140,7 +140,7 @@ public override void fetchResults(IPricingEngineResults r) if (!results.legNPV.empty()) { Utils.QL_REQUIRE( results.legNPV.Count == legNPV_.Count, () => "wrong number of leg NPV returned" ); - legNPV_ = results.legNPV; + legNPV_ = new List( results.legNPV); } else { @@ -150,7 +150,7 @@ public override void fetchResults(IPricingEngineResults r) if (!results.legBPS.empty()) { Utils.QL_REQUIRE( results.legBPS.Count == legBPS_.Count, () => "wrong number of leg BPS returned" ); - legBPS_ = results.legBPS; + legBPS_ = new List(results.legBPS); } else { @@ -160,7 +160,7 @@ public override void fetchResults(IPricingEngineResults r) if (!results.startDiscounts.empty()) { Utils.QL_REQUIRE( results.startDiscounts.Count == startDiscounts_.Count, () => "wrong number of leg start discounts returned" ); - startDiscounts_ = results.startDiscounts; + startDiscounts_ = new List(results.startDiscounts); } else { @@ -170,7 +170,7 @@ public override void fetchResults(IPricingEngineResults r) if (!results.endDiscounts.empty()) { Utils.QL_REQUIRE( results.endDiscounts.Count == endDiscounts_.Count, () => "wrong number of leg end discounts returned" ); - endDiscounts_ = results.endDiscounts; + endDiscounts_ = new List(results.endDiscounts); } else { @@ -259,19 +259,35 @@ public virtual void validate() public new class Results : Instrument.Results { - public InitializedList legNPV = new InitializedList(); - public InitializedList legBPS = new InitializedList(); - public InitializedList startDiscounts = new InitializedList(); - public InitializedList endDiscounts = new InitializedList(); + public List legNPV ; + public List legBPS ; + public List startDiscounts ; + public List endDiscounts ; public double? npvDateDiscount; public override void reset() { base.reset(); // clear all previous results - legNPV.Erase(); - legBPS.Erase(); - startDiscounts.Erase(); - endDiscounts.Erase(); + if ( legNPV == null) + legNPV = new List(); + else + legNPV.Clear(); + + if ( legBPS == null ) + legBPS = new List(); + else + legBPS.Clear(); + + if ( startDiscounts == null ) + startDiscounts = new List(); + else + startDiscounts.Clear(); + + if ( endDiscounts == null ) + endDiscounts = new List(); + else + endDiscounts.Clear(); + npvDateDiscount = null; } } diff --git a/QLNet/Instruments/VanillaSwap.cs b/QLNet/Instruments/VanillaSwap.cs index 35059158c..051de5f2a 100644 --- a/QLNet/Instruments/VanillaSwap.cs +++ b/QLNet/Instruments/VanillaSwap.cs @@ -269,7 +269,7 @@ public override void fetchResults(IPricingEngineResults r) //! %Arguments for simple swap calculation - new public class Arguments : Swap.Arguments + public new class Arguments : Swap.Arguments { public Type type; public double nominal; diff --git a/QLNet/Math/Interpolations/BicubicSplineInterpolation.cs b/QLNet/Math/Interpolations/BicubicSplineInterpolation.cs index e2c2a3622..3452d8796 100644 --- a/QLNet/Math/Interpolations/BicubicSplineInterpolation.cs +++ b/QLNet/Math/Interpolations/BicubicSplineInterpolation.cs @@ -5,13 +5,13 @@ 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 + 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. @@ -21,7 +21,7 @@ under the terms of the QLNet license. You should have received a namespace QLNet { - public interface IBicubicSplineDerivatives + public interface IBicubicSplineDerivatives { double derivativeX( double x, double y ); double derivativeY( double x, double y ); @@ -33,12 +33,12 @@ public interface IBicubicSplineDerivatives public class BicubicSplineImpl : Interpolation2D.templateImpl,IBicubicSplineDerivatives { public BicubicSplineImpl(List xBegin, int size, List yBegin,int ySize,Matrix zData) - : base(xBegin,size, yBegin,ySize,zData) + : base(xBegin,size, yBegin,ySize,zData) { calculate(); } - - public override void calculate() + + public override void calculate() { splines_ = new List (this.zData_.rows()); for (int i=0; i<(this.zData_.rows()); ++i) @@ -46,10 +46,10 @@ public override void calculate() CubicInterpolation.DerivativeApprox.Spline, false, CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0, CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0)); - + } - - public override double value(double x, double y) + + public override double value(double x, double y) { List section = new InitializedList( splines_.Count ); for (int i=0; i section = new InitializedList(this.zData_.columns()); - for (int i=0; i < section.Count; ++i) + for (int i=0; i < section.Count; ++i) { section[i] = value(this.xBegin_[i], y); } - + return new CubicInterpolation( this.xBegin_, this.xSize_, section, CubicInterpolation.DerivativeApprox.Spline, false, CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0, CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0).derivative(x); } - - - public double secondDerivativeX(double x, double y) + + + public double secondDerivativeX(double x, double y) { List section = new InitializedList( this.zData_.columns() ); - for (int i=0; i < section.Count; ++i) + for (int i=0; i < section.Count; ++i) { section[i] = value(this.xBegin_[i], y); } - + return new CubicInterpolation( this.xBegin_, this.xSize_, section, CubicInterpolation.DerivativeApprox.Spline, false, CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0, CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0).secondDerivative(x); } - - public double derivativeY(double x, double y) + + public double derivativeY(double x, double y) { List section = new InitializedList( splines_.Count ); for (int i=0; i section = new InitializedList( splines_.Count ); for (int i=0; i section = new InitializedList( this.zData_.columns() ); - for (int i=0; i < section.Count; ++i) + for (int i=0; i < section.Count; ++i) { section[i] = derivativeY(this.xBegin_[i], y); } - + return new CubicInterpolation( this.xBegin_, this.xSize_, section, CubicInterpolation.DerivativeApprox.Spline, false, CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0, - CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0).derivative(x); + CubicInterpolation.BoundaryCondition.SecondDerivative, 0.0).derivative(x); } - - + + private List splines_; - + } - + //! bicubic-spline interpolation between discrete points /*! \todo revise end conditions */ - public class BicubicSpline : Interpolation2D + public class BicubicSpline : Interpolation2D { /*! \pre the \f$ x \f$ and \f$ y \f$ values must be sorted. */ - public BicubicSpline( List xBegin, int size, List yBegin,int ySize,Matrix zData) + public BicubicSpline( List xBegin, int size, List yBegin,int ySize,Matrix zData) { impl_ = new BicubicSplineImpl(xBegin, size, yBegin, ySize, zData); } - + public double derivativeX(double x, double y) { return ((IBicubicSplineDerivatives)impl_).derivativeX(x, y); } - + public double derivativeY(double x, double y) { return ( (IBicubicSplineDerivatives)impl_ ).derivativeY( x, y ); } @@ -165,14 +165,14 @@ public double secondDerivativeY(double x, double y) { public double derivativeXY( double x, double y ) { - return ( (IBicubicSplineDerivatives)impl_ ).derivativeXY( x, y ); + return ( (IBicubicSplineDerivatives)impl_ ).derivativeXY( x, y ); } } //! bicubic-spline-interpolation factory - public class Bicubic - { - public Interpolation2D interpolate(List xBegin, int size, List yBegin,int ySize,Matrix zData) + public class Bicubic : IInterpolationFactory2D + { + public Interpolation2D interpolate(List xBegin, int size, List yBegin,int ySize,Matrix zData) { return new BicubicSpline( xBegin, size, yBegin, ySize, zData ); } diff --git a/QLNet/Math/Interpolations/CubicInterpolation.cs b/QLNet/Math/Interpolations/CubicInterpolation.cs index a1482a268..ef3001a0a 100644 --- a/QLNet/Math/Interpolations/CubicInterpolation.cs +++ b/QLNet/Math/Interpolations/CubicInterpolation.cs @@ -167,8 +167,8 @@ public class CubicInterpolationImpl : Interpolation.templateImpl { // a[i]*(x-x[i]) + // b[i]*(x-x[i])^2 + // c[i]*(x-x[i])^3 - public InitializedList primitiveConst_, a_, b_, c_; - InitializedList monotonicityAdjustments_; + public List primitiveConst_, a_, b_, c_; + List monotonicityAdjustments_; public CubicInterpolationImpl(List xBegin, int size, List yBegin, diff --git a/QLNet/Math/Interpolations/Linearinterpolation.cs b/QLNet/Math/Interpolations/Linearinterpolation.cs index f2824ae24..4bac3e4e0 100644 --- a/QLNet/Math/Interpolations/Linearinterpolation.cs +++ b/QLNet/Math/Interpolations/Linearinterpolation.cs @@ -21,7 +21,7 @@ under the terms of the QLNet license. You should have received a namespace QLNet { /* linear interpolation between discrete points */ public class LinearInterpolationImpl : Interpolation.templateImpl { - private InitializedList primitiveConst_, s_; + private List primitiveConst_, s_; public LinearInterpolationImpl(List xBegin, int size, List yBegin) : base(xBegin, size, yBegin) { diff --git a/QLNet/Math/Interpolations/VannaVolgaInterpolation.cs b/QLNet/Math/Interpolations/VannaVolgaInterpolation.cs new file mode 100644 index 000000000..c4373d37a --- /dev/null +++ b/QLNet/Math/Interpolations/VannaVolgaInterpolation.cs @@ -0,0 +1,142 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 VannaVolgaInterpolationImpl : Interpolation.templateImpl + { + public VannaVolgaInterpolationImpl(List xBegin, int size, List yBegin, + double spot, double dDiscount, double fDiscount, double T) + : base(xBegin, size, yBegin, VannaVolga.requiredPoints) + { + spot_ = spot; + dDiscount_ = dDiscount; + fDiscount_ = fDiscount; + T_ = T; + + premiaBS = new List(); + premiaMKT = new List(); + vegas = new List(); + + Utils.QL_REQUIRE(size == 3, ()=> "Vanna Volga Interpolator only interpolates 3 volatilities in strike space"); + } + + public override void update() + { + //atmVol should be the second vol + atmVol_ = this.yBegin_[1]; + fwd_ = spot_*fDiscount_/dDiscount_; + for(int i = 0; i < 3; i++) + { + premiaBS.Add(Utils.blackFormula(Option.Type.Call, xBegin_[i], fwd_, atmVol_ * Math.Sqrt(T_), dDiscount_)); + premiaMKT.Add(Utils.blackFormula(Option.Type.Call, xBegin_[i], fwd_, yBegin_[i] * Math.Sqrt(T_), dDiscount_)); + vegas.Add(vega(xBegin_[i])); + } + } + + public override double value(double k) + { + double x1 = vega(k)/vegas[0] + * (Math.Log(xBegin_[1]/k) * Math.Log(xBegin_[2]/k)) + / (Math.Log(xBegin_[1]/xBegin_[0]) * Math.Log(xBegin_[2]/xBegin_[0])); + double x2 = vega(k)/vegas[1] + * (Math.Log(k/xBegin_[0]) * Math.Log(xBegin_[2]/k)) + / (Math.Log(xBegin_[1]/xBegin_[0]) * Math.Log(xBegin_[2]/xBegin_[1])); + double x3 = vega(k)/vegas[2] + * (Math.Log(k/xBegin_[0]) * Math.Log(k/xBegin_[1])) + / (Math.Log(xBegin_[2]/xBegin_[0]) * Math.Log(xBegin_[2]/xBegin_[1])); + + double cBS = Utils.blackFormula(Option.Type.Call, k, fwd_, atmVol_ * Math.Sqrt(T_), dDiscount_); + double c = cBS + x1*(premiaMKT[0] - premiaBS[0]) + x2*(premiaMKT[1] - premiaBS[1]) + x3*(premiaMKT[2] - premiaBS[2]); + double std = Utils.blackFormulaImpliedStdDev(Option.Type.Call, k, fwd_, c, dDiscount_); + return std / Math.Sqrt(T_); + } + + public override double primitive(double x) + { + Utils.QL_FAIL("Vanna Volga primitive not implemented"); + return 0; + } + + public override double derivative(double x) + { + Utils.QL_FAIL("Vanna Volga derivative not implemented"); + return 0; + } + + public override double secondDerivative(double x) + { + Utils.QL_FAIL("Vanna Volga secondDerivative not implemented"); + return 0; + } + + + private List premiaBS; + private List premiaMKT; + private List vegas; + private double atmVol_; + private double spot_; + private double fwd_; + private double dDiscount_; + private double fDiscount_; + private double T_; + + private double vega(double k) + { + double d1 = (Math.Log(fwd_/k) + 0.5 * Math.Pow(atmVol_, 2.0) * T_)/(atmVol_ * Math.Sqrt(T_)); + NormalDistribution norm = new NormalDistribution(); + return spot_ * dDiscount_ * Math.Sqrt(T_) * norm.value(d1); + } + + } + public class VannaVolgaInterpolation : Interpolation + { + /*! \pre the \f$ x \f$ values must be sorted. */ + public VannaVolgaInterpolation(List xBegin, int size, List yBegin, + double spot,double dDiscount,double fDiscount,double T) + { + impl_ = new VannaVolgaInterpolationImpl( xBegin, size, yBegin, spot, dDiscount, fDiscount, T); + impl_.update(); + } + + } + + //! %VannaVolga-interpolation factory and traits + public class VannaVolga + { + public VannaVolga(double spot,double dDiscount,double fDiscount,double T) + { + spot_ = spot; + dDiscount_ = dDiscount; + fDiscount_ = fDiscount; + T_ = T; + } + + public Interpolation interpolate(List xBegin, int size,List yBegin) + { + return new VannaVolgaInterpolation(xBegin, size, yBegin, spot_, dDiscount_, fDiscount_, T_); + } + + public static int requiredPoints = 3; + + private double spot_; + private double dDiscount_; + private double fDiscount_; + private double T_; + } +} diff --git a/QLNet/Math/Matrix.cs b/QLNet/Math/Matrix.cs index d876e6036..0dfba567d 100644 --- a/QLNet/Math/Matrix.cs +++ b/QLNet/Math/Matrix.cs @@ -154,7 +154,129 @@ public static Matrix transpose(Matrix m) { for (int j=0; j "matrix is not square" ); + int n = m.rows(); + Matrix result = new Matrix( n, n ); + for ( int i = 0; i < n; ++i ) + for ( int j = 0; j < n; ++j ) + result[i,j] = m[i,j]; + + Matrix lum; + int[] perm; + decompose( m, out lum, out perm ); + + double[] b = new double[n]; + for ( int i = 0; i < n; ++i ) + { + for ( int j = 0; j < n; ++j ) + if ( i == perm[j] ) + b[j] = 1.0; + else + b[j] = 0.0; + + double[] x = Helper( lum, b ); // + for ( int j = 0; j < n; ++j ) + result[j,i] = x[j]; + } + return result; + } + + // Crout's LU decomposition for matrix determinant and inverse + // stores combined lower & upper in lum[][] + // stores row permuations into perm[] + // returns +1 or -1 according to even or odd number of row permutations + // lower gets dummy 1.0s on diagonal (0.0s above) + // upper gets lum values on diagonal (0.0s below) + public static int decompose( Matrix m, out Matrix lum, out int[] perm ) + { + int toggle = +1; // even (+1) or odd (-1) row permutatuions + int n = m.rows_; + + // Make a copy of Matrix m into result Matrix lu + lum = new Matrix( n, n ); + for ( int i = 0; i < n; ++i ) + for ( int j = 0; j < n; ++j ) + lum[i,j] = m[i,j]; + + + // make perm[] + perm = new int[n]; + for ( int i = 0; i < n; ++i ) + perm[i] = i; + + for ( int j = 0; j < n - 1; ++j ) // process by column. note n-1 + { + double max = Math.Abs( lum[j,j] ); + int piv = j; + + for ( int i = j + 1; i < n; ++i ) // find pivot index + { + double xij = Math.Abs( lum[i,j] ); + if ( xij > max ) + { + max = xij; + piv = i; + } + } // i + + if ( piv != j ) + { + lum.swapRow( piv, j); + + int t = perm[piv]; // swap perm elements + perm[piv] = perm[j]; + perm[j] = t; + + toggle = -toggle; + } + + double xjj = lum[j,j]; + if ( xjj != 0.0 ) + { + for ( int i = j + 1; i < n; ++i ) + { + double xij = lum[i,j] / xjj; + lum[i,j] = xij; + for ( int k = j + 1; k < n; ++k ) + lum[i,k] -= xij * lum[j,k]; + } + } + + } // j + + return toggle; + } + + public static double[] Helper( Matrix luMatrix, double[] b ) // helper + { + int n = luMatrix.rows_; + double[] x = new double[n]; + b.CopyTo( x, 0 ); + + for ( int i = 1; i < n; ++i ) + { + double sum = x[i]; + for ( int j = 0; j < i; ++j ) + sum -= luMatrix[i,j] * x[j]; + x[i] = sum; + } + + x[n - 1] /= luMatrix[n - 1,n - 1]; + for ( int i = n - 2; i >= 0; --i ) + { + double sum = x[i]; + for ( int j = i + 1; j < n; ++j ) + sum -= luMatrix[i,j] * x[j]; + x[i] = sum / luMatrix[i,i]; + } + + return x; + } // Helper + public static Matrix outerProduct(List v1begin, List v2begin) { @@ -181,8 +303,17 @@ public void swap(int i1, int j1, int i2, int j2) { double t = this[i2, j2]; this[i2, j2] = this[i1, j1]; this[i1, j1] = t; + } + + public void swapRow( int r1, int r2 ) + { + Vector t = this.row(r1); + for (int i = 0; i < this.columns_; i++) + this[r1, i] = this[r2, i]; + + for ( int i = 0; i < this.columns_; i++ ) + this[r2, i] = t[i]; } - public override string ToString() { String to = string.Empty; diff --git a/QLNet/Math/Optimization/Simplex.cs b/QLNet/Math/Optimization/Simplex.cs index c1ee3c48d..6855833d2 100644 --- a/QLNet/Math/Optimization/Simplex.cs +++ b/QLNet/Math/Optimization/Simplex.cs @@ -17,12 +17,13 @@ under the terms of the QLNet license. You should have received a FOR A PARTICULAR PURPOSE. See the license for more details. */ using System; +using System.Collections.Generic; namespace QLNet { public static partial class Utils { // Computes the size of the simplex - public static double computeSimplexSize(InitializedList vertices) + public static double computeSimplexSize(List vertices) { Vector center = new Vector(vertices[0].Count, 0); for (int i = 0; i < vertices.Count; ++i) @@ -216,7 +217,7 @@ private double extrapolate(ref Problem P, int iHighest, ref double factor) } private double lambda_; - private InitializedList vertices_; + private List vertices_; private Vector values_; private Vector sum_; } diff --git a/QLNet/Methods/lattices/bsmlattice.cs b/QLNet/Methods/lattices/bsmlattice.cs index 5d9fb97c0..7fe79c0d4 100644 --- a/QLNet/Methods/lattices/bsmlattice.cs +++ b/QLNet/Methods/lattices/bsmlattice.cs @@ -1,5 +1,6 @@ /* Copyright (C) 2008 Siarhei Novik (snovik@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -15,45 +16,89 @@ under the terms of the QLNet license. You should have received a 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 { - // this is just a wrapper for QL compatibility - public class BlackScholesLattice : BlackScholesLattice where T : ITree { - public BlackScholesLattice(ITree tree, double riskFreeRate, double end, int steps) - : base(tree, riskFreeRate, end, steps) { } - } - - //! Simple binomial lattice approximating the Black-Scholes model - /*! \ingroup lattices */ - public class BlackScholesLattice : TreeLattice1D, IGenericLattice { - private ITree tree_; - private double discount_; - private double pd_, pu_; - - public BlackScholesLattice(ITree tree, double riskFreeRate, double end, int steps) - : base(new TimeGrid(end, steps), 2) { - tree_ = tree; - discount_ = Math.Exp(-riskFreeRate*(end/steps)); - pd_ = tree.probability(0,0,0); - pu_ = tree.probability(0,0,1); - } - - public int size(int i) { return tree_.size(i); } - public double discount(int i, int j) { return discount_; } - - public override void stepback(int i, Vector values, Vector newValues){ - for (int j=0; j : BlackScholesLattice where T : ITree + { + public BlackScholesLattice(ITree tree, double riskFreeRate, double end, int steps) + : base(tree, riskFreeRate, end, steps) + {} + } + + //! Simple binomial lattice approximating the Black-Scholes model + /*! \ingroup lattices */ + + public class BlackScholesLattice : TreeLattice1D, IGenericLattice + { + public BlackScholesLattice(ITree tree, double riskFreeRate, double end, int steps) + : base(new TimeGrid(end, steps), 2) + { + tree_ = tree; + riskFreeRate_ = riskFreeRate; + dt_ = end/steps; + discount_ = Math.Exp(-riskFreeRate*(end/steps)); + pd_ = tree.probability(0, 0, 0); + pu_ = tree.probability(0, 0, 1); + } + + public double riskFreeRate() + { + return riskFreeRate_; + } + + public double dt() + { + return dt_; + } + + public int size(int i) + { + return tree_.size(i); + } + + public double discount(int i, int j) + { + return discount_; + } + + public override void stepback(int i, Vector values, Vector newValues) + { + for (int j = 0; j < size(i); j++) + newValues[j] = (pd_*values[j] + pu_*values[j + 1])*discount_; + } + + public override double underlying(int i, int index) + { + return tree_.underlying(i, index); + } + + public int descendant(int i, int index, int branch) + { + return tree_.descendant(i, index, branch); + } + + public double probability(int i, int index, int branch) + { + return tree_.probability(i, index, branch); + } + + // this is a workaround for CuriouslyRecurringTemplate of TreeLattice + // recheck it + protected override BlackScholesLattice impl() + { + return this; + } + + protected ITree tree_; + protected double riskFreeRate_; + protected double dt_; + private double discount_; + private double pd_, pu_; + + } } diff --git a/QLNet/Models/Parameter.cs b/QLNet/Models/Parameter.cs index 6b204eb1e..510a57ea8 100644 --- a/QLNet/Models/Parameter.cs +++ b/QLNet/Models/Parameter.cs @@ -63,7 +63,7 @@ public abstract class Impl //! Standard constant parameter \f$ a(t) = a \f$ public class ConstantParameter : Parameter { - new private class Impl : Parameter.Impl + private new class Impl : Parameter.Impl { public override double value( Vector parameters, double UnnamedParameter1 ) { @@ -89,7 +89,7 @@ public ConstantParameter( double value, Constraint constraint ) //! %Parameter which is always zero \f$ a(t) = 0 \f$ public class NullParameter : Parameter { - new private class Impl : Parameter.Impl + private new class Impl : Parameter.Impl { public override double value( Vector UnnamedParameter1, double UnnamedParameter2 ) { @@ -109,7 +109,7 @@ public NullParameter() // public class PiecewiseConstantParameter : Parameter { - new private class Impl : Parameter.Impl + private new class Impl : Parameter.Impl { public Impl( List times ) { diff --git a/QLNet/Models/Shortrate/calibrationhelpers/caphelper.cs b/QLNet/Models/Shortrate/calibrationhelpers/caphelper.cs index 451598a83..40fef85ec 100644 --- a/QLNet/Models/Shortrate/calibrationhelpers/caphelper.cs +++ b/QLNet/Models/Shortrate/calibrationhelpers/caphelper.cs @@ -102,7 +102,7 @@ protected override void performCalculations() termStructure_.link.dayCounter(), termStructure_ ); - InitializedList nominals = new InitializedList( 1, 1.0 ); + List nominals = new InitializedList( 1, 1.0 ); Schedule floatSchedule = new Schedule( startDate, maturity, index_.tenor(), index_.fixingCalendar(), diff --git a/QLNet/Pricingengines/BlackDeltaCalculator.cs b/QLNet/Pricingengines/BlackDeltaCalculator.cs new file mode 100644 index 000000000..1c98c81b2 --- /dev/null +++ b/QLNet/Pricingengines/BlackDeltaCalculator.cs @@ -0,0 +1,406 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Black delta calculator class + /*! Class includes many operations needed for different applications + in FX markets, which has special quoation mechanisms, since + every price can be expressed in both numeraires. + */ + public class BlackDeltaCalculator + { + // A parsimonious constructor is chosen, which for example + // doesn't need a strike. The reason for this is, that we'd + // like this class to calculate deltas for different strikes + // many times, e.g. in a numerical routine, which will be the + // case in the smile setup procedure. + public BlackDeltaCalculator(Option.Type ot, + DeltaVolQuote.DeltaType dt, + double spot, + double dDiscount, // domestic discount + double fDiscount, // foreign discount + double stdDev) + { + dt_ = dt; + ot_ = ot; + dDiscount_ = dDiscount; + fDiscount_ = fDiscount; + stdDev_ = stdDev; + spot_ = spot; + forward_ = spot*fDiscount/dDiscount; + phi_ = (int)ot; + + + Utils.QL_REQUIRE(spot_>0.0,()=> "positive spot value required: " + spot_ + " not allowed"); + Utils.QL_REQUIRE(dDiscount_>0.0,()=> "positive domestic discount factor required: " + dDiscount_ + " not allowed"); + Utils.QL_REQUIRE(fDiscount_>0.0,()=> "positive foreign discount factor required: " + fDiscount_ + " not allowed"); + Utils.QL_REQUIRE(stdDev_>=0.0,()=> "non-negative standard deviation required: " + stdDev_ + " not allowed"); + + fExpPos_ =forward_*Math.Exp(0.5*stdDev_*stdDev_); + fExpNeg_ =forward_*Math.Exp(-0.5*stdDev_*stdDev_); + } + + // Give strike, receive delta according to specified type + public double deltaFromStrike(double strike) + { + Utils.QL_REQUIRE(strike >=0.0,()=> "positive strike value required: " + strike + " not allowed"); + + double res=0.0; + + switch(dt_) + { + case DeltaVolQuote.DeltaType.Spot: + res=phi_*fDiscount_*cumD1(strike); + break; + + case DeltaVolQuote.DeltaType.Fwd: + res=phi_*cumD1(strike); + break; + + case DeltaVolQuote.DeltaType.PaSpot: + res=phi_*fDiscount_*cumD2(strike)*strike/forward_; + break; + + case DeltaVolQuote.DeltaType.PaFwd: + res=phi_*cumD2(strike)*strike/forward_; + break; + + default: + Utils.QL_FAIL("invalid delta type"); + break; + } + return res; + + } + + // Give delta according to specified type, receive strike + public double strikeFromDelta(double delta) + { + return strikeFromDelta(delta, dt_); + } + + public double cumD1(double strike) // N(d1) or N(-d1) + { + double d1_=0.0; + double cum_d1_pos_ = 1.0; // N(d1) + double cum_d1_neg_ = 0.0; // N(-d1) + + CumulativeNormalDistribution f = new CumulativeNormalDistribution(); + + if (stdDev_>=Const.QL_EPSILON) + { + if(strike>0) + { + d1_ = Math.Log(forward_/strike)/stdDev_ + 0.5*stdDev_; + return f.value(phi_*d1_); + } + } + else + { + if (forward_0) + { + // if Call + return cum_d1_pos_; + } + else + { + return cum_d1_neg_; + } + + } + public double cumD2(double strike) // N(d2) or N(-d2) + { + double d2_=0.0; + double cum_d2_pos_= 1.0; // N(d2) + double cum_d2_neg_= 0.0; // N(-d2) + + CumulativeNormalDistribution f = new CumulativeNormalDistribution(); + + if (stdDev_>=Const.QL_EPSILON) + { + if(strike>0) + { + d2_ = Math.Log(forward_/strike)/stdDev_ - 0.5*stdDev_; + return f.value(phi_*d2_); + } + + } + else + { + if (forward_0) + { + // if Call + return cum_d2_pos_; + } + else + { + return cum_d2_neg_; + } + } + + public double nD1(double strike) // n(d1) + { + double d1_=0.0; + double n_d1_ = 0.0; // n(d1) + + if (stdDev_>=Const.QL_EPSILON) + { + if(strike>0) + { + d1_ = Math.Log(forward_/strike)/stdDev_ + 0.5*stdDev_; + CumulativeNormalDistribution f = new CumulativeNormalDistribution(); + n_d1_ = f.derivative(d1_); + } + } + + return n_d1_; + + } + public double nD2(double strike) // n(d2) + { + double d2_=0.0; + double n_d2_= 0.0; // n(d2) + + if (stdDev_>=Const.QL_EPSILON) + { + if(strike>0) + { + d2_ = Math.Log(forward_/strike)/stdDev_ - 0.5*stdDev_; + CumulativeNormalDistribution f = new CumulativeNormalDistribution(); + n_d2_ = f.derivative(d2_); + } + } + + return n_d2_; + } + + public void setDeltaType( DeltaVolQuote.DeltaType dt ) { dt_ = dt; } + public void setOptionType( Option.Type ot ) + { + ot_ = ot; + phi_ = (int) ot_ ; + } + + // The following function can be calculated without an explicit strike + public double atmStrike(DeltaVolQuote.AtmType atmT) + { + double res=0.0; + + switch(atmT) + { + case DeltaVolQuote.AtmType.AtmDeltaNeutral: + if(dt_==DeltaVolQuote.DeltaType.Spot || dt_==DeltaVolQuote.DeltaType.Fwd) + { + res=fExpPos_; + } + else + { + res=fExpNeg_; + } + break; + + case DeltaVolQuote.AtmType.AtmFwd: + res=forward_; + break; + + case DeltaVolQuote.AtmType.AtmGammaMax: + case DeltaVolQuote.AtmType.AtmVegaMax: + res=fExpPos_; + break; + + case DeltaVolQuote.AtmType.AtmPutCall50: + Utils.QL_REQUIRE(dt_==DeltaVolQuote.DeltaType.Fwd,()=> + "|PutDelta|=CallDelta=0.50 only possible for forward delta."); + res=fExpPos_; + break; + + default: + Utils.QL_FAIL("invalid atm type"); + break; + } + + return res; + } + + + // alternative delta type + private double strikeFromDelta(double delta, DeltaVolQuote.DeltaType dt) + { + double res=0.0; + double arg=0.0; + InverseCumulativeNormal f = new InverseCumulativeNormal(); + + Utils.QL_REQUIRE(delta*phi_>=0.0,()=> "Option type and delta are incoherent."); + + switch ( dt ) + { + case DeltaVolQuote.DeltaType.Spot: + Utils.QL_REQUIRE( Math.Abs( delta ) <= fDiscount_, () => "Spot delta out of range." ); + arg = -phi_ * f.value( phi_ * delta / fDiscount_ ) * stdDev_ + 0.5 * stdDev_ * stdDev_; + res = forward_ * Math.Exp( arg ); + break; + + case DeltaVolQuote.DeltaType.Fwd: + Utils.QL_REQUIRE( Math.Abs( delta ) <= 1.0, () => "Forward delta out of range." ); + arg = -phi_ * f.value( phi_ * delta ) * stdDev_ + 0.5 * stdDev_ * stdDev_; + res = forward_ * Math.Exp( arg ); + break; + + case DeltaVolQuote.DeltaType.PaSpot: + case DeltaVolQuote.DeltaType.PaFwd: + // This has to be solved numerically. One of the + // problems is that the premium adjusted call delta is + // not monotonic in strike, such that two solutions + // might occur. The one right to the max of the delta is + // considered to be the correct strike. Some proper + // interval bounds for the strike need to be chosen, the + // numerics can otherwise be very unreliable and + // unstable. I've chosen Brent over Newton, since the + // interval can be specified explicitly and we can not + // run into the area on the left of the maximum. The + // put delta doesn't have this property and can be + // solved without any problems, but also numerically. + + BlackDeltaPremiumAdjustedSolverClass f1 = new BlackDeltaPremiumAdjustedSolverClass( + ot_, dt, spot_, dDiscount_, fDiscount_, stdDev_, delta ); + + Brent solver = new Brent(); + solver.setMaxEvaluations( 1000 ); + double accuracy = 1.0e-10; + + double rightLimit = 0.0; + double leftLimit = 0.0; + + // Strike of not premium adjusted is always to the right of premium adjusted + if ( dt == DeltaVolQuote.DeltaType.PaSpot ) + { + rightLimit = strikeFromDelta( delta, DeltaVolQuote.DeltaType.Spot ); + } + else + { + rightLimit = strikeFromDelta( delta, DeltaVolQuote.DeltaType.Fwd ); + } + + if ( phi_ < 0 ) + { + // if put + res = solver.solve( f1, accuracy, rightLimit, 0.0, spot_ * 100.0 ); + break; + } + else + { + // find out the left limit which is the strike + // corresponding to the value where premium adjusted + // deltas have their maximum. + + BlackDeltaPremiumAdjustedMaxStrikeClass g = new BlackDeltaPremiumAdjustedMaxStrikeClass( + ot_, dt, spot_, dDiscount_, fDiscount_, stdDev_ ); + + leftLimit = solver.solve( g, accuracy, rightLimit * 0.5, 0.0, rightLimit ); + + double guess = leftLimit + ( rightLimit - leftLimit ) * 0.5; + + res = solver.solve( f1, accuracy, guess, leftLimit, rightLimit ); + } // end if phi<0 else + + break; + + + default: + Utils.QL_FAIL( "invalid delta type" ); + break; + } + + return res; + } + + private DeltaVolQuote.DeltaType dt_; + private Option.Type ot_; + private double dDiscount_, fDiscount_; + + private double stdDev_, spot_, forward_; + private int phi_; + private double fExpPos_,fExpNeg_; + + } + + + public class BlackDeltaPremiumAdjustedSolverClass : ISolver1d + { + public BlackDeltaPremiumAdjustedSolverClass( Option.Type ot, + DeltaVolQuote.DeltaType dt, + double spot, + double dDiscount, // domestic discount + double fDiscount, // foreign discount + double stdDev, + double delta) + { + bdc_ = new BlackDeltaCalculator(ot,dt,spot,dDiscount,fDiscount,stdDev); + delta_ = delta; + } + + public override double value( double strike ) { return bdc_.deltaFromStrike( strike ) - delta_; } + + private BlackDeltaCalculator bdc_; + private double delta_; + } + + public class BlackDeltaPremiumAdjustedMaxStrikeClass : ISolver1d + { + public BlackDeltaPremiumAdjustedMaxStrikeClass( Option.Type ot, + DeltaVolQuote.DeltaType dt, + double spot, + double dDiscount, // domestic discount + double fDiscount, // foreign discount + double stdDev) + { + bdc_ = new BlackDeltaCalculator(ot,dt,spot,dDiscount,fDiscount,stdDev); + stdDev_ = stdDev; + } + + public override double value( double strike ) { return bdc_.cumD2( strike ) * stdDev_ - bdc_.nD2( strike ); } + + private BlackDeltaCalculator bdc_; + private double stdDev_; + } + +} diff --git a/QLNet/Pricingengines/Bond/TreeCallableBondEngine.cs b/QLNet/Pricingengines/Bond/TreeCallableBondEngine.cs index b824bec45..8f4bd9823 100644 --- a/QLNet/Pricingengines/Bond/TreeCallableBondEngine.cs +++ b/QLNet/Pricingengines/Bond/TreeCallableBondEngine.cs @@ -55,7 +55,7 @@ public override void calculate() Date referenceDate; DayCounter dayCounter; - ITermStructureConsistentModel tsmodel = (ITermStructureConsistentModel)base.model_; + ITermStructureConsistentModel tsmodel = (ITermStructureConsistentModel)base.model_.link; if (tsmodel != null) { referenceDate = tsmodel.termStructure().link.referenceDate(); diff --git a/QLNet/Pricingengines/Cliquet/AnalyticCliquetEngine.cs b/QLNet/Pricingengines/Cliquet/AnalyticCliquetEngine.cs new file mode 100644 index 000000000..bae9d2b9d --- /dev/null +++ b/QLNet/Pricingengines/Cliquet/AnalyticCliquetEngine.cs @@ -0,0 +1,108 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for Cliquet options using analytical formulae + /*! \ingroup cliquetengines + + \test + - the correctness of the returned value is tested by + reproducing results available in literature. + - the correctness of the returned greeks is tested by + reproducing numerical derivatives. + */ + public class AnalyticCliquetEngine : CliquetOption.Engine + { + public AnalyticCliquetEngine( GeneralizedBlackScholesProcess process) + { + process_=process; + process_.registerWith(update); + } + + public override void calculate() + { + Utils.QL_REQUIRE(arguments_.accruedCoupon == null && + arguments_.lastFixing == null,()=> + "this engine cannot price options already started"); + Utils.QL_REQUIRE(arguments_.localCap == null && + arguments_.localFloor == null && + arguments_.globalCap == null && + arguments_.globalFloor == null,()=> + "this engine cannot price capped/floored options"); + + Utils.QL_REQUIRE(arguments_.exercise.type() == Exercise.Type.European,()=> "not an European option"); + + PercentageStrikePayoff moneyness = arguments_.payoff as PercentageStrikePayoff; + Utils.QL_REQUIRE(moneyness!=null,()=> "wrong payoff given"); + + List resetDates = arguments_.resetDates; + resetDates.Add(arguments_.exercise.lastDate()); + + double underlying = process_.stateVariable().link.value(); + Utils.QL_REQUIRE(underlying > 0.0,()=> "negative or null underlying"); + double strike = underlying * moneyness.strike(); + StrikedTypePayoff payoff = new PlainVanillaPayoff(moneyness.optionType(),strike); + + results_.value = 0.0; + results_.delta = results_.gamma = 0.0; + results_.theta = 0.0; + results_.rho = results_.dividendRho = 0.0; + results_.vega = 0.0; + + for (int i = 1; i < resetDates.Count; i++) + { + double weight = process_.dividendYield().link.discount(resetDates[i-1]); + double discount = process_.riskFreeRate().link.discount(resetDates[i]) / + process_.riskFreeRate().link.discount(resetDates[i-1]); + double qDiscount = process_.dividendYield().link.discount(resetDates[i]) / + process_.dividendYield().link.discount(resetDates[i-1]); + double forward = underlying*qDiscount/discount; + double variance = process_.blackVolatility().link.blackForwardVariance(resetDates[i-1],resetDates[i],strike); + + BlackCalculator black = new BlackCalculator(payoff, forward, Math.Sqrt(variance), discount); + + DayCounter rfdc = process_.riskFreeRate().link.dayCounter(); + DayCounter divdc = process_.dividendYield().link.dayCounter(); + DayCounter voldc = process_.blackVolatility().link.dayCounter(); + + results_.value += weight * black.value(); + results_.delta += weight * (black.delta(underlying) + + moneyness.strike() * discount * + black.beta()); + results_.gamma += 0.0; + results_.theta += process_.dividendYield().link.forwardRate( + resetDates[i-1], resetDates[i], rfdc, Compounding.Continuous, Frequency.NoFrequency).value() * + weight * black.value(); + + double dt = rfdc.yearFraction(resetDates[i-1],resetDates[i]); + results_.rho += weight * black.rho(dt); + + double t = divdc.yearFraction( process_.dividendYield().link.referenceDate(),resetDates[i-1]); + dt = divdc.yearFraction(resetDates[i-1],resetDates[i]); + results_.dividendRho += weight * (black.dividendRho(dt) - + t * black.value()); + + dt = voldc.yearFraction(resetDates[i-1], resetDates[i]); + results_.vega += weight * black.vega(dt); + } + } + + private GeneralizedBlackScholesProcess process_; + } +} diff --git a/QLNet/Pricingengines/Cliquet/AnalyticPerformanceEngine.cs b/QLNet/Pricingengines/Cliquet/AnalyticPerformanceEngine.cs new file mode 100644 index 000000000..fb28dc21e --- /dev/null +++ b/QLNet/Pricingengines/Cliquet/AnalyticPerformanceEngine.cs @@ -0,0 +1,105 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for performance options using analytical formulae + /*! \ingroup cliquetengines + + \test the correctness of the returned greeks is tested by + reproducing numerical derivatives. + */ + public class AnalyticPerformanceEngine : CliquetOption.Engine + { + public AnalyticPerformanceEngine(GeneralizedBlackScholesProcess process) + { + process_ = process; + process_.registerWith( update ); + } + public override void calculate() + { + Utils.QL_REQUIRE(arguments_.accruedCoupon == null && + arguments_.lastFixing == null,()=> + "this engine cannot price options already started"); + Utils.QL_REQUIRE(arguments_.localCap == null && + arguments_.localFloor == null && + arguments_.globalCap == null && + arguments_.globalFloor == null,()=> + "this engine cannot price capped/floored options"); + + Utils.QL_REQUIRE(arguments_.exercise.type() == Exercise.Type.European,()=>"not an European option"); + + PercentageStrikePayoff moneyness = arguments_.payoff as PercentageStrikePayoff; + Utils.QL_REQUIRE(moneyness!= null,()=> "wrong payoff given"); + + List resetDates = arguments_.resetDates; + resetDates.Add(arguments_.exercise.lastDate()); + + double underlying = process_.stateVariable().link.value(); + Utils.QL_REQUIRE(underlying > 0.0,()=> "negative or null underlying"); + + StrikedTypePayoff payoff = new PlainVanillaPayoff(moneyness.optionType(), 1.0); + + results_.value = 0.0; + results_.delta = results_.gamma = 0.0; + results_.theta = 0.0; + results_.rho = results_.dividendRho = 0.0; + results_.vega = 0.0; + + for (int i = 1; i < resetDates.Count; i++) + { + double discount = process_.riskFreeRate().link.discount(resetDates[i-1]); + double rDiscount = process_.riskFreeRate().link.discount(resetDates[i]) / + process_.riskFreeRate().link.discount(resetDates[i-1]); + double qDiscount = process_.dividendYield().link.discount(resetDates[i]) / + process_.dividendYield().link.discount(resetDates[i-1]); + double forward = (1.0/moneyness.strike())*qDiscount/rDiscount; + double variance = process_.blackVolatility().link.blackForwardVariance( + resetDates[i-1],resetDates[i], + underlying * moneyness.strike()); + + BlackCalculator black = new BlackCalculator(payoff, forward, Math.Sqrt(variance), rDiscount); + + DayCounter rfdc = process_.riskFreeRate().link.dayCounter(); + DayCounter divdc = process_.dividendYield().link.dayCounter(); + DayCounter voldc = process_.blackVolatility().link.dayCounter(); + + results_.value += discount * moneyness.strike() * black.value(); + results_.delta += 0.0; + results_.gamma += 0.0; + results_.theta += process_.riskFreeRate().link.forwardRate( + resetDates[i-1], resetDates[i], rfdc, Compounding.Continuous, Frequency.NoFrequency).value() * + discount * moneyness.strike() * black.value(); + + double dt = rfdc.yearFraction(resetDates[i-1],resetDates[i]); + double t = rfdc.yearFraction( process_.riskFreeRate().link.referenceDate(),resetDates[i-1]); + results_.rho += discount * moneyness.strike() * (black.rho(dt) - t * black.value()); + + dt = divdc.yearFraction(resetDates[i-1],resetDates[i]); + results_.dividendRho += discount * moneyness.strike() * black.dividendRho(dt); + + dt = voldc.yearFraction(resetDates[i-1], resetDates[i]); + results_.vega += discount * moneyness.strike() * black.vega(dt); + } + + } + + + private GeneralizedBlackScholesProcess process_; + } +} diff --git a/QLNet/Pricingengines/Forward/ForwardPerformanceVanillaEngine.cs b/QLNet/Pricingengines/Forward/ForwardPerformanceVanillaEngine.cs new file mode 100644 index 000000000..715baf39b --- /dev/null +++ b/QLNet/Pricingengines/Forward/ForwardPerformanceVanillaEngine.cs @@ -0,0 +1,64 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +using System.Text; + +namespace QLNet +{ + //! %Forward performance engine for vanilla options + /*! \ingroup forwardengines + + \test + - the correctness of the returned value is tested by + reproducing results available in literature. + - the correctness of the returned greeks is tested by + reproducing numerical derivatives. + */ + public class ForwardPerformanceVanillaEngine : ForwardVanillaEngine + { + public ForwardPerformanceVanillaEngine( GeneralizedBlackScholesProcess process, GetOriginalEngine getEngine ) + : base( process, getEngine ) { } + public override void calculate() + { + this.setup(); + this.originalEngine_.calculate(); + getOriginalResults(); + } + protected override void getOriginalResults() + { + DayCounter rfdc = this.process_.riskFreeRate().link.dayCounter(); + double resetTime = rfdc.yearFraction(this.process_.riskFreeRate().link.referenceDate(), + this.arguments_.resetDate ); + double discR = this.process_.riskFreeRate().link.discount(this.arguments_.resetDate ); + // it's a performance option + discR /= this.process_.stateVariable().link.value(); + + double? temp = this.originalResults_.value; + this.results_.value = discR * temp; + this.results_.delta = 0.0; + this.results_.gamma = 0.0; + this.results_.theta = this.process_.riskFreeRate().link. + zeroRate( this.arguments_.resetDate, rfdc, Compounding.Continuous, Frequency.NoFrequency ).value() + * this.results_.value; + this.results_.vega = discR * this.originalResults_.vega; + this.results_.rho = -resetTime * this.results_.value + discR * this.originalResults_.rho; + this.results_.dividendRho = discR * this.originalResults_.dividendRho; + + } + } +} diff --git a/QLNet/Pricingengines/Forward/ForwardVanillaEngine.cs b/QLNet/Pricingengines/Forward/ForwardVanillaEngine.cs new file mode 100644 index 000000000..d0e1ea61e --- /dev/null +++ b/QLNet/Pricingengines/Forward/ForwardVanillaEngine.cs @@ -0,0 +1,128 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +using System.Text; + +namespace QLNet +{ + //! %Forward engine for vanilla options + /*! \ingroup forwardengines + + \test + - the correctness of the returned value is tested by + reproducing results available in literature. + - the correctness of the returned greeks is tested by + reproducing numerical derivatives. + */ + public class ForwardVanillaEngine : GenericEngine + { + + public delegate IPricingEngine GetOriginalEngine( GeneralizedBlackScholesProcess process ); + + public ForwardVanillaEngine( GeneralizedBlackScholesProcess process, GetOriginalEngine getEngine) + { + process_ = process; + process_.registerWith(update); + getOriginalEngine_ = getEngine; + } + public override void calculate() + { + setup(); + originalEngine_.calculate(); + getOriginalResults(); + } + + protected void setup() + { + StrikedTypePayoff argumentsPayoff = this.arguments_.payoff as StrikedTypePayoff; + Utils.QL_REQUIRE(argumentsPayoff != null,()=> "wrong payoff given"); + + StrikedTypePayoff payoff = new PlainVanillaPayoff(argumentsPayoff.optionType(), + this.arguments_.moneyness * process_.x0()); + + // maybe the forward value is "better", in some fashion + // the right level is needed in order to interpolate + // the vol + Handle spot = process_.stateVariable(); + Utils.QL_REQUIRE(spot.link.value() >= 0.0,()=> "negative or null underlting given"); + Handle dividendYield = new Handle( + new ImpliedTermStructure(process_.dividendYield(),this.arguments_.resetDate)); + Handle riskFreeRate = new Handle( + new ImpliedTermStructure(process_.riskFreeRate(),this.arguments_.resetDate)); + // The following approach is ok if the vol is at most + // time dependant. It is plain wrong if it is asset dependant. + // In the latter case the right solution would be stochastic + // volatility or at least local volatility (which unfortunately + // implies an unrealistic time-decreasing smile) + Handle blackVolatility= new Handle( + new ImpliedVolTermStructure(process_.blackVolatility(),this.arguments_.resetDate)); + + GeneralizedBlackScholesProcess fwdProcess = new GeneralizedBlackScholesProcess(spot, dividendYield, + riskFreeRate,blackVolatility); + + + originalEngine_ = getOriginalEngine_(fwdProcess); + originalEngine_.reset(); + + originalArguments_ = originalEngine_.getArguments() as Option.Arguments; + Utils.QL_REQUIRE(originalArguments_!= null,()=> "wrong engine type"); + originalResults_ = originalEngine_.getResults() as OneAssetOption.Results; + Utils.QL_REQUIRE(originalResults_!= null,()=> "wrong engine type"); + + originalArguments_.payoff = payoff; + originalArguments_.exercise = this.arguments_.exercise; + + originalArguments_.validate(); + + } + + protected virtual void getOriginalResults() + { + DayCounter rfdc = process_.riskFreeRate().link.dayCounter(); + DayCounter divdc = process_.dividendYield().link.dayCounter(); + double resetTime = rfdc.yearFraction( process_.riskFreeRate().link.referenceDate(),this.arguments_.resetDate ); + double discQ = process_.dividendYield().link.discount(this.arguments_.resetDate ); + + this.results_.value = discQ * originalResults_.value; + // I need the strike derivative here ... + if ( originalResults_.delta != null && originalResults_.strikeSensitivity != null ) + { + this.results_.delta = discQ * ( originalResults_.delta + + this.arguments_.moneyness * originalResults_.strikeSensitivity ); + } + this.results_.gamma = 0.0; + this.results_.theta = process_.dividendYield().link. + zeroRate( this.arguments_.resetDate, divdc, Compounding.Continuous, Frequency.NoFrequency ).value() + * this.results_.value; + if ( originalResults_.vega != null) + this.results_.vega = discQ * originalResults_.vega; + if ( originalResults_.rho != null ) + this.results_.rho = discQ * originalResults_.rho; + if ( originalResults_.dividendRho != null ) + { + this.results_.dividendRho = -resetTime * this.results_.value + + discQ * originalResults_.dividendRho; + } + } + protected GeneralizedBlackScholesProcess process_; + protected IPricingEngine originalEngine_; + protected Option.Arguments originalArguments_; + protected OneAssetOption.Results originalResults_; + protected GetOriginalEngine getOriginalEngine_; + } +} diff --git a/QLNet/Pricingengines/Lookback/AnalyticContinuousFixedLookbackEngine.cs b/QLNet/Pricingengines/Lookback/AnalyticContinuousFixedLookbackEngine.cs new file mode 100644 index 000000000..7748e0d07 --- /dev/null +++ b/QLNet/Pricingengines/Lookback/AnalyticContinuousFixedLookbackEngine.cs @@ -0,0 +1,125 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for European continuous fixed-strike lookback + /*! Formula from "Option Pricing Formulas", + E.G. Haug, McGraw-Hill, 1998, p.63-64 + */ + public class AnalyticContinuousFixedLookbackEngine : ContinuousFixedLookbackOption.Engine + { + public AnalyticContinuousFixedLookbackEngine(GeneralizedBlackScholesProcess process) + { + process_ = process; + process_.registerWith(update); + } + public override void calculate() + { + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "Non-plain payoff given"); + + Utils.QL_REQUIRE(process_.x0() > 0.0,()=> "negative or null underlying"); + + double strike = payoff.strike(); + + switch (payoff.optionType()) + { + case Option.Type.Call: + Utils.QL_REQUIRE(payoff.strike()>=0.0,()=>"Strike must be positive or null"); + if (strike <= minmax()) + results_.value = A(1) + C(1); + else + results_.value = B(1); + break; + case Option.Type.Put: + Utils.QL_REQUIRE(payoff.strike()>0.0,()=>"Strike must be positive"); + if (strike >= minmax()) + results_.value = A(-1) + C(-1); + else + results_.value = B(-1); + break; + default: + Utils.QL_FAIL("Unknown type"); + break; + } + } + + private GeneralizedBlackScholesProcess process_; + private CumulativeNormalDistribution f_ = new CumulativeNormalDistribution(); + // helper methods + private double underlying() { return process_.x0(); } + private double strike() + { + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "Non-plain payoff given"); + return payoff.strike(); + } + private double residualTime() { return process_.time( arguments_.exercise.lastDate() ); } + private double volatility() { return process_.blackVolatility().link.blackVol( residualTime(), strike() ); } + private double minmax() { return arguments_.minmax.GetValueOrDefault(); } + private double stdDeviation() {return volatility() * Math.Sqrt(residualTime());} + private double riskFreeRate() + { + return process_.riskFreeRate().link.zeroRate( residualTime(), + Compounding.Continuous,Frequency.NoFrequency ).value(); + } + private double riskFreeDiscount() { return process_.riskFreeRate().link.discount( residualTime() ); } + private double dividendYield() + { + return process_.dividendYield().link.zeroRate( residualTime(), + Compounding.Continuous, Frequency.NoFrequency ).value(); + } + private double dividendDiscount() { return process_.dividendYield().link.discount( residualTime() ); } + private double A( double eta ) + { + double vol = volatility(); + double lambda = 2.0*(riskFreeRate() - dividendYield())/(vol*vol); + double ss = underlying()/minmax(); + double d1 = Math.Log(ss)/stdDeviation() + 0.5*(lambda+1.0)*stdDeviation(); + double N1 = f_.value(eta*d1); + double N2 = f_.value(eta*(d1-stdDeviation())); + double N3 = f_.value(eta*(d1-lambda*stdDeviation())); + double N4 = f_.value(eta*d1); + double powss = Math.Pow(ss, -lambda); + return eta*(underlying() * dividendDiscount() * N1 - + minmax() * riskFreeDiscount() * N2 - + underlying() * riskFreeDiscount() * + (powss * N3 - dividendDiscount()* N4/riskFreeDiscount())/lambda); + } + private double B( double eta ) + { + double vol = volatility(); + double lambda = 2.0*(riskFreeRate() - dividendYield())/(vol*vol); + double ss = underlying()/strike(); + double d1 = Math.Log(ss)/stdDeviation() + 0.5*(lambda+1.0)*stdDeviation(); + double N1 = f_.value(eta*d1); + double N2 = f_.value(eta*(d1-stdDeviation())); + double N3 = f_.value(eta*(d1-lambda*stdDeviation())); + double N4 = f_.value(eta*d1); + double powss = Math.Pow(ss, -lambda); + return eta*(underlying() * dividendDiscount() * N1 - + strike() * riskFreeDiscount() * N2 - + underlying() * riskFreeDiscount() * + (powss * N3 - dividendDiscount()* N4/riskFreeDiscount())/lambda); + } + private double C( double eta ) + { + return eta * ( riskFreeDiscount() * ( minmax() - strike() ) ); + } + } +} diff --git a/QLNet/Pricingengines/Lookback/AnalyticContinuousFloatingLookbackEngine.cs b/QLNet/Pricingengines/Lookback/AnalyticContinuousFloatingLookbackEngine.cs new file mode 100644 index 000000000..895830b32 --- /dev/null +++ b/QLNet/Pricingengines/Lookback/AnalyticContinuousFloatingLookbackEngine.cs @@ -0,0 +1,89 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for European continuous floating-strike lookback + /*! Formula from "Option Pricing Formulas", + E.G. Haug, McGraw-Hill, 1998, p.61-62 + */ + public class AnalyticContinuousFloatingLookbackEngine : ContinuousFloatingLookbackOption.Engine + { + public AnalyticContinuousFloatingLookbackEngine(GeneralizedBlackScholesProcess process) + { + process_=process; + process_.registerWith(update); + } + public override void calculate() + { + FloatingTypePayoff payoff = arguments_.payoff as FloatingTypePayoff; + Utils.QL_REQUIRE(payoff != null,()=> "Non-floating payoff given"); + + Utils.QL_REQUIRE(process_.x0() > 0.0,()=> "negative or null underlying"); + + switch (payoff.optionType()) + { + case Option.Type.Call: + results_.value = A(1); + break; + case Option.Type.Put: + results_.value = A(-1); + break; + default: + Utils.QL_FAIL("Unknown type"); + break; + } + } + + private GeneralizedBlackScholesProcess process_; + private CumulativeNormalDistribution f_ = new CumulativeNormalDistribution(); + // helper methods + private double underlying() { return process_.x0(); } + private double residualTime() { return process_.time( arguments_.exercise.lastDate() ); } + private double volatility() { return process_.blackVolatility().link.blackVol( residualTime(), minmax() ); } + private double minmax() { return arguments_.minmax.GetValueOrDefault(); } + private double stdDeviation() {return volatility() * Math.Sqrt(residualTime());} + private double riskFreeRate() + { + return process_.riskFreeRate().link.zeroRate( residualTime(), + Compounding.Continuous,Frequency.NoFrequency ).value(); + } + private double riskFreeDiscount() { return process_.riskFreeRate().link.discount( residualTime() ); } + private double dividendYield() + { + return process_.dividendYield().link.zeroRate( residualTime(), + Compounding.Continuous, Frequency.NoFrequency ).value(); + } + private double dividendDiscount() { return process_.dividendYield().link.discount( residualTime() ); } + private double A( double eta ) + { + double vol = volatility(); + double lambda = 2.0*(riskFreeRate() - dividendYield())/(vol*vol); + double s = underlying()/minmax(); + double d1 = Math.Log(s)/stdDeviation() + 0.5*(lambda+1.0)*stdDeviation(); + double n1 = f_.value(eta*d1); + double n2 = f_.value(eta*(d1-stdDeviation())); + double n3 = f_.value(eta*(-d1+lambda*stdDeviation())); + double n4 = f_.value(eta*-d1); + double pow_s = Math.Pow(s, -lambda); + return eta*((underlying() * dividendDiscount() * n1 - + minmax() * riskFreeDiscount() * n2) + + (underlying() * riskFreeDiscount() * + (pow_s * n3 - dividendDiscount()* n4/riskFreeDiscount())/lambda)); + } + } +} diff --git a/QLNet/Pricingengines/Lookback/AnalyticContinuousPartialFixedLookbackEngine.cs b/QLNet/Pricingengines/Lookback/AnalyticContinuousPartialFixedLookbackEngine.cs new file mode 100644 index 000000000..ab2e56aef --- /dev/null +++ b/QLNet/Pricingengines/Lookback/AnalyticContinuousPartialFixedLookbackEngine.cs @@ -0,0 +1,134 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for European continuous partial-time fixed-strike lookback options + /*! Formula from "Option Pricing Formulas, Second Edition", + E.G. Haug, 2006, p.148 + */ + public class AnalyticContinuousPartialFixedLookbackEngine : ContinuousPartialFixedLookbackOption.Engine + { + public AnalyticContinuousPartialFixedLookbackEngine(GeneralizedBlackScholesProcess process) + { + process_ = process; + process_.registerWith(update); + } + public override void calculate() + { + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "Non-plain payoff given"); + + Utils.QL_REQUIRE(process_.x0() > 0.0,()=> "negative or null underlying"); + + switch (payoff.optionType()) + { + case Option.Type.Call: + Utils.QL_REQUIRE(payoff.strike()>=0.0,()=>"Strike must be positive or null"); + results_.value = A(1); + break; + case Option.Type.Put: + Utils.QL_REQUIRE(payoff.strike()>0.0,()=>"Strike must be positive"); + results_.value = A(-1); + break; + default: + Utils.QL_FAIL("Unknown type"); + break; + } + } + + private GeneralizedBlackScholesProcess process_; + private CumulativeNormalDistribution f_ = new CumulativeNormalDistribution(); + // helper methods + private double underlying() {return process_.x0();} + private double strike() + { + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "Non-plain payoff given"); + return payoff.strike(); + } + private double residualTime() { return process_.time( arguments_.exercise.lastDate() ); } + private double volatility() { return process_.blackVolatility().link.blackVol( residualTime(), strike() ); } + private double lookbackPeriodStartTime() { return process_.time( arguments_.lookbackPeriodStart ); } + private double stdDeviation() {return volatility() * Math.Sqrt(residualTime());} + private double riskFreeRate() + { + return process_.riskFreeRate().link.zeroRate( residualTime(), + Compounding.Continuous,Frequency.NoFrequency ).value(); + } + private double riskFreeDiscount() { return process_.riskFreeRate().link.discount( residualTime() ); } + private double dividendYield() + { + return process_.dividendYield().link.zeroRate( residualTime(), + Compounding.Continuous, Frequency.NoFrequency ).value(); + } + private double dividendDiscount() { return process_.dividendYield().link.discount( residualTime() ); } + private double A(double eta) + { + bool differentStartOfLookback = lookbackPeriodStartTime() != residualTime(); + double carry = riskFreeRate() - dividendYield(); + + double vol = volatility(); + double x = 2.0*carry/(vol*vol); + double s = underlying()/strike(); + double ls = Math.Log(s); + double d1 = ls/stdDeviation() + 0.5*(x+1.0)*stdDeviation(); + double d2 = d1 - stdDeviation(); + + double e1 = 0, e2 = 0; + if (differentStartOfLookback) + { + e1 = (carry + vol * vol / 2) * (residualTime() - lookbackPeriodStartTime()) / (vol * Math.Sqrt(residualTime() - lookbackPeriodStartTime())); + e2 = e1 - vol * Math.Sqrt(residualTime() - lookbackPeriodStartTime()); + } + + double f1 = (ls + (carry + vol * vol / 2) * lookbackPeriodStartTime()) / (vol * Math.Sqrt(lookbackPeriodStartTime())); + double f2 = f1 - vol * Math.Sqrt(lookbackPeriodStartTime()); + + double n1 = f_.value(eta*d1); + double n2 = f_.value(eta*d2); + + BivariateCumulativeNormalDistributionWe04DP cnbn1 = new BivariateCumulativeNormalDistributionWe04DP(-1), + cnbn2= new BivariateCumulativeNormalDistributionWe04DP(0), + cnbn3= new BivariateCumulativeNormalDistributionWe04DP(0); + if (differentStartOfLookback) { + cnbn1 = new BivariateCumulativeNormalDistributionWe04DP (-Math.Sqrt(lookbackPeriodStartTime() / residualTime())); + cnbn2 = new BivariateCumulativeNormalDistributionWe04DP (Math.Sqrt(1 - lookbackPeriodStartTime() / residualTime())); + cnbn3 = new BivariateCumulativeNormalDistributionWe04DP (-Math.Sqrt(1 - lookbackPeriodStartTime() / residualTime())); + } + + double n3 = cnbn1.value(eta*(d1-x*stdDeviation()), eta*(-f1+2.0* carry * Math.Sqrt(lookbackPeriodStartTime()) / vol)); + double n4 = cnbn2.value(eta*e1, eta*d1); + double n5 = cnbn3.value(-eta*e1, eta*d1); + double n6 = cnbn1.value(eta*f2, -eta*d2); + double n7 = f_.value(eta*f1); + double n8 = f_.value(-eta*e2); + + double pow_s = Math.Pow(s, -x); + double carryDiscount = Math.Exp(-carry * (residualTime() - lookbackPeriodStartTime())); + return eta*(underlying() * dividendDiscount() * n1 + - strike() * riskFreeDiscount() * n2 + + underlying() * riskFreeDiscount() / x + * (-pow_s * n3 + dividendDiscount() / riskFreeDiscount() * n4) + - underlying() * dividendDiscount() * n5 + - strike() * riskFreeDiscount() * n6 + + carryDiscount * dividendDiscount() + * (1 - 0.5 * vol * vol / carry) * + underlying() * n7 * n8); + } + } +} diff --git a/QLNet/Pricingengines/Lookback/AnalyticContinuousPartialFloatingLookbackEngine.cs b/QLNet/Pricingengines/Lookback/AnalyticContinuousPartialFloatingLookbackEngine.cs new file mode 100644 index 000000000..0d5908ad5 --- /dev/null +++ b/QLNet/Pricingengines/Lookback/AnalyticContinuousPartialFloatingLookbackEngine.cs @@ -0,0 +1,155 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for European continuous partial-time floating-strike lookback option + /*! Formula from "Option Pricing Formulas, Second Edition", + E.G. Haug, 2006, p.146 + + */ + public class AnalyticContinuousPartialFloatingLookbackEngine : ContinuousPartialFloatingLookbackOption.Engine + { + public AnalyticContinuousPartialFloatingLookbackEngine(GeneralizedBlackScholesProcess process) + { + process_ = process; + process_.registerWith( update ); + } + public override void calculate() + { + FloatingTypePayoff payoff = arguments_.payoff as FloatingTypePayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "Non-floating payoff given"); + + Utils.QL_REQUIRE(process_.x0() > 0.0,()=> "negative or null underlying"); + + switch (payoff.optionType()) + { + case Option.Type.Call: + results_.value = A(1); + break; + case Option.Type.Put: + results_.value = A(-1); + break; + default: + Utils.QL_FAIL("Unknown type"); + break; + } + } + + private GeneralizedBlackScholesProcess process_; + private CumulativeNormalDistribution f_ = new CumulativeNormalDistribution(); + // helper methods + private double underlying() { return process_.x0(); } + private double residualTime() { return process_.time( arguments_.exercise.lastDate() ); } + private double volatility() { return process_.blackVolatility().link.blackVol( residualTime(), minmax() ); } + private double minmax() { return arguments_.minmax.GetValueOrDefault(); } + private double lambda() { return arguments_.lambda; } + private double lookbackPeriodEndTime() { return process_.time( arguments_.lookbackPeriodEnd ); } + private double stdDeviation() {return volatility() * Math.Sqrt(residualTime());} + private double riskFreeRate() + { + return process_.riskFreeRate().link.zeroRate( residualTime(), Compounding.Continuous, + Frequency.NoFrequency ).value(); + } + private double riskFreeDiscount() { return process_.riskFreeRate().link.discount( residualTime() ); } + private double dividendYield() + { + return process_.dividendYield().link.zeroRate( residualTime(), + Compounding.Continuous, Frequency.NoFrequency ).value(); + } + private double dividendDiscount() { return process_.dividendYield().link.discount( residualTime() ); } + private double A(double eta) + { + bool fullLookbackPeriod = lookbackPeriodEndTime() == residualTime(); + double carry = riskFreeRate() - dividendYield(); + double vol = volatility(); + double x = 2.0*carry/(vol*vol); + double s = underlying()/minmax(); + + double ls = Math.Log(s); + double d1 = ls/stdDeviation() + 0.5*(x+1.0)*stdDeviation(); + double d2 = d1 - stdDeviation(); + + double e1 = 0, e2 = 0; + if (!fullLookbackPeriod) + { + e1 = (carry + vol * vol / 2) * (residualTime() - lookbackPeriodEndTime()) / (vol * Math.Sqrt(residualTime() - lookbackPeriodEndTime())); + e2 = e1 - vol * Math.Sqrt(residualTime() - lookbackPeriodEndTime()); + } + + double f1 = (ls + (carry + vol * vol / 2) * lookbackPeriodEndTime()) / (vol * Math.Sqrt(lookbackPeriodEndTime())); + double f2 = f1 - vol * Math.Sqrt(lookbackPeriodEndTime()); + + double l1 = Math.Log(lambda()) / vol; + double g1 = l1 / Math.Sqrt(residualTime()); + double g2 = 0; + if (!fullLookbackPeriod) g2 = l1 / Math.Sqrt(residualTime() - lookbackPeriodEndTime()); + + double n1 = f_.value(eta*(d1 - g1)); + double n2 = f_.value(eta*(d2 - g1)); + + BivariateCumulativeNormalDistributionWe04DP cnbn1 = new BivariateCumulativeNormalDistributionWe04DP(1), + cnbn2 = new BivariateCumulativeNormalDistributionWe04DP(0), + cnbn3 = new BivariateCumulativeNormalDistributionWe04DP(-1); + if (!fullLookbackPeriod) + { + cnbn1 = new BivariateCumulativeNormalDistributionWe04DP (Math.Sqrt(lookbackPeriodEndTime() / residualTime())); + cnbn2 = new BivariateCumulativeNormalDistributionWe04DP (-Math.Sqrt(1 - lookbackPeriodEndTime() / residualTime())); + cnbn3 = new BivariateCumulativeNormalDistributionWe04DP (-Math.Sqrt(lookbackPeriodEndTime() / residualTime())); + } + + double n3 = cnbn1.value(eta*(-f1+2.0* carry * Math.Sqrt(lookbackPeriodEndTime()) / vol), eta*(-d1+x*stdDeviation()-g1)); + double n4 = 0, n5 = 0, n6 = 0, n7 = 0; + if (!fullLookbackPeriod) + { + n4 = cnbn2.value(-eta*(d1+g1), eta*(e1 + g2)); + n5 = cnbn2.value(-eta*(d1-g1), eta*(e1 - g2)); + n6 = cnbn3.value(eta*-f2, eta*(d2 - g1)); + n7 = f_.value(eta*(e2 - g2)); + } + else + { + n4 = f_.value(-eta*(d1+g1)); + } + + double n8 = f_.value(-eta*f1); + double pow_s = Math.Pow(s, -x); + double pow_l = Math.Pow(lambda(), x); + + if (!fullLookbackPeriod) + { + return eta*(underlying() * dividendDiscount() * n1 - + lambda() * minmax() * riskFreeDiscount() * n2 + + underlying() * riskFreeDiscount() * lambda() / x * + (pow_s * n3 - dividendDiscount() / riskFreeDiscount() * pow_l * n4) + + underlying() * dividendDiscount() * n5 + + riskFreeDiscount() * lambda() * minmax() * n6 - + Math.Exp(-carry * (residualTime() - lookbackPeriodEndTime())) * + dividendDiscount() * (1 + 0.5 * vol * vol / carry) * lambda() * + underlying() * n7 * n8); + } + else + { + //Simpler calculation + return eta*(underlying() * dividendDiscount() * n1 - + lambda() * minmax() * riskFreeDiscount() * n2 + + underlying() * riskFreeDiscount() * lambda() / x * + (pow_s * n3 - dividendDiscount() / riskFreeDiscount() * pow_l * n4)); + } + } + } +} diff --git a/QLNet/Pricingengines/asian/mc_discr_arith_av_price.cs b/QLNet/Pricingengines/asian/mc_discr_arith_av_price.cs index 17e706624..c03f35f1d 100644 --- a/QLNet/Pricingengines/asian/mc_discr_arith_av_price.cs +++ b/QLNet/Pricingengines/asian/mc_discr_arith_av_price.cs @@ -97,7 +97,7 @@ protected override IPricingEngine controlPricingEngine() { } } - public class ArithmeticAPOPathPricer : PathPricer + public class ArithmeticAPOPathPricer : PathPricer { private PlainVanillaPayoff payoff_; @@ -156,11 +156,11 @@ public double value(Path path) } - /*public double value(IPath path){ - if (!(path.length() > 0)) - throw new Exception("the path cannot be empty"); - return payoff_.value((path as Path).back()) * discount_; - }*/ + public double value(IPath path) + { + Utils.QL_REQUIRE( path.length() > 0, () => "the path cannot be empty" ); + return payoff_.value(((Path) path).back()) * discount_; + } } // public class MakeMCDiscreteArithmeticAPEngine diff --git a/QLNet/Pricingengines/barrier/AnalyticBinaryBarrierEngine.cs b/QLNet/Pricingengines/barrier/AnalyticBinaryBarrierEngine.cs new file mode 100644 index 000000000..02b0ab346 --- /dev/null +++ b/QLNet/Pricingengines/barrier/AnalyticBinaryBarrierEngine.cs @@ -0,0 +1,344 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Analytic pricing engine for American binary barriers options + /*! The formulas are taken from "The complete guide to option pricing formulas 2nd Ed", + E.G. Haug, McGraw-Hill, p.176 and following. + + \ingroup barrierengines + + \test + - the correctness of the returned value in case of + cash-or-nothing at-expiry binary payoff is tested by + reproducing results available in literature. + - the correctness of the returned value in case of + asset-or-nothing at-expiry binary payoff is tested by + reproducing results available in literature. + */ + public class AnalyticBinaryBarrierEngine : BarrierOption.Engine + { + public AnalyticBinaryBarrierEngine( GeneralizedBlackScholesProcess process) + { + process_ = process; + process_.registerWith(update); + } + public override void calculate() + { + AmericanExercise ex = arguments_.exercise as AmericanExercise; + Utils.QL_REQUIRE(ex!=null,()=> "non-American exercise given"); + Utils.QL_REQUIRE(ex.payoffAtExpiry(),()=> "payoff must be at expiry"); + Utils.QL_REQUIRE(ex.dates()[0] <= process_.blackVolatility().link.referenceDate(),()=> + "American option with window exercise not handled yet"); + + StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "non-striked payoff given"); + + double spot = process_.stateVariable().link.value(); + Utils.QL_REQUIRE(spot > 0.0,()=> "negative or null underlying given"); + + double variance = process_.blackVolatility().link.blackVariance(ex.lastDate(),payoff.strike()); + double? barrier = arguments_.barrier; + Utils.QL_REQUIRE(barrier>0.0,()=>"positive barrier value required"); + Barrier.Type barrierType = arguments_.barrierType; + + // KO degenerate cases + if ( (barrierType == Barrier.Type.DownOut && spot <= barrier) || + (barrierType == Barrier.Type.UpOut && spot >= barrier)) + { + // knocked out, no value + results_.value = 0; + results_.delta = 0; + results_.gamma = 0; + results_.vega = 0; + results_.theta = 0; + results_.rho = 0; + results_.dividendRho = 0; + return; + } + + // KI degenerate cases + if ((barrierType == Barrier.Type.DownIn && spot <= barrier) || + (barrierType == Barrier.Type.UpIn && spot >= barrier)) + { + // knocked in - is a digital european + Exercise exercise = new EuropeanExercise(arguments_.exercise.lastDate()); + + IPricingEngine engine = new AnalyticEuropeanEngine(process_); + + VanillaOption opt = new VanillaOption(payoff, exercise); + opt.setPricingEngine(engine); + results_.value = opt.NPV(); + results_.delta = opt.delta(); + results_.gamma = opt.gamma(); + results_.vega = opt.vega(); + results_.theta = opt.theta(); + results_.rho = opt.rho(); + results_.dividendRho = opt.dividendRho(); + return; + } + + double riskFreeDiscount = process_.riskFreeRate().link.discount(ex.lastDate()); + + AnalyticBinaryBarrierEngine_helper helper = new AnalyticBinaryBarrierEngine_helper( + process_, payoff, ex, arguments_ ); + results_.value = helper.payoffAtExpiry(spot, variance, riskFreeDiscount); + + } + + private GeneralizedBlackScholesProcess process_; + } + + // calc helper object + public class AnalyticBinaryBarrierEngine_helper + { + + public AnalyticBinaryBarrierEngine_helper( + GeneralizedBlackScholesProcess process, + StrikedTypePayoff payoff, + AmericanExercise exercise, + BarrierOption.Arguments arguments) + { + process_ = process; + payoff_ = payoff; + exercise_ = exercise; + arguments_ = arguments; + } + + public double payoffAtExpiry(double spot, double variance, double discount) + { + double dividendDiscount = process_.dividendYield().link.discount(exercise_.lastDate()); + + Utils.QL_REQUIRE(spot>0.0,()=> "positive spot value required"); + Utils.QL_REQUIRE(discount>0.0,()=> "positive discount required"); + Utils.QL_REQUIRE(dividendDiscount>0.0,()=> "positive dividend discount required"); + Utils.QL_REQUIRE(variance>=0.0,()=> "negative variance not allowed"); + + Option.Type type = payoff_.optionType(); + double strike = payoff_.strike(); + double? barrier = arguments_.barrier; + Utils.QL_REQUIRE(barrier>0.0,()=>"positive barrier value required"); + Barrier.Type barrierType = arguments_.barrierType; + + double stdDev = Math.Sqrt(variance); + double mu = Math.Log(dividendDiscount/discount)/variance - 0.5; + double K = 0; + + // binary cash-or-nothing payoff? + CashOrNothingPayoff coo = payoff_ as CashOrNothingPayoff; + if (coo != null ) + { + K = coo.cashPayoff(); + } + + // binary asset-or-nothing payoff? + AssetOrNothingPayoff aoo = payoff_ as AssetOrNothingPayoff; + if (aoo!=null) + { + mu += 1.0; + K = spot * dividendDiscount / discount; // forward + } + + double log_S_X = Math.Log(spot/strike); + double log_S_H = Math.Log(spot/barrier.GetValueOrDefault()); + double log_H_S = Math.Log(barrier.GetValueOrDefault()/spot); + double log_H2_SX = Math.Log(barrier.GetValueOrDefault()*barrier.GetValueOrDefault()/(spot*strike)); + double H_S_2mu = Math.Pow(barrier.GetValueOrDefault()/spot, 2*mu); + + double eta = (barrierType == Barrier.Type.DownIn || + barrierType == Barrier.Type.DownOut ? 1.0 : -1.0); + double phi = (type == Option.Type.Call ? 1.0 : -1.0); + + double x1, x2, y1, y2; + double cum_x1, cum_x2, cum_y1, cum_y2; + if (variance>=Const.QL_EPSILON) + { + // we calculate using mu*stddev instead of (mu+1)*stddev + // because cash-or-nothing don't need it. asset-or-nothing + // mu is really mu+1 + x1 = phi*(log_S_X/stdDev + mu*stdDev); + x2 = phi*(log_S_H/stdDev + mu*stdDev); + y1 = eta*(log_H2_SX/stdDev + mu*stdDev); + y2 = eta*(log_H_S/stdDev + mu*stdDev); + + CumulativeNormalDistribution f = new CumulativeNormalDistribution(); + cum_x1 = f.value(x1); + cum_x2 = f.value(x2); + cum_y1 = f.value(y1); + cum_y2 = f.value(y2); + } + else + { + if (log_S_X>0) + cum_x1= 1.0; + else + cum_x1= 0.0; + if (log_S_H>0) + cum_x2= 1.0; + else + cum_x2= 0.0; + if (log_H2_SX>0) + cum_y1= 1.0; + else + cum_y1= 0.0; + if (log_H_S>0) + cum_y2= 1.0; + else + cum_y2= 0.0; + } + + double alpha = 0; + + switch (barrierType) + { + case Barrier.Type.DownIn: + if (type == Option.Type.Call) + { + // down-in and call + if (strike >= barrier) + { + // B3 (eta=1, phi=1) + alpha = H_S_2mu * cum_y1; + } + else + { + // B1-B2+B4 (eta=1, phi=1) + alpha = cum_x1 - cum_x2 + H_S_2mu * cum_y2; + } + } + else + { + // down-in and put + if (strike >= barrier) + { + // B2-B3+B4 (eta=1, phi=-1) + alpha = cum_x2 + H_S_2mu*(-cum_y1 + cum_y2); + } + else + { + // B1 (eta=1, phi=-1) + alpha = cum_x1; + } + } + break; + + case Barrier.Type.UpIn: + if (type == Option.Type.Call) + { + // up-in and call + if (strike >= barrier) + { + // B1 (eta=-1, phi=1) + alpha = cum_x1; + } + else + { + // B2-B3+B4 (eta=-1, phi=1) + alpha = cum_x2 + H_S_2mu * (-cum_y1 + cum_y2); + } + } + else + { + // up-in and put + if (strike >= barrier) + { + // B1-B2+B4 (eta=-1, phi=-1) + alpha = cum_x1 - cum_x2 + H_S_2mu * cum_y2; + } + else + { + // B3 (eta=-1, phi=-1) + alpha = H_S_2mu * cum_y1; + } + } + break; + + case Barrier.Type.DownOut: + if (type == Option.Type.Call) + { + // down-out and call + if (strike >= barrier) + { + // B1-B3 (eta=1, phi=1) + alpha = cum_x1 - H_S_2mu * cum_y1; + } + else + { + // B2-B4 (eta=1, phi=1) + alpha = cum_x2 - H_S_2mu * cum_y2; + } + } + else + { + // down-out and put + if (strike >= barrier) + { + // B1-B2+B3-B4 (eta=1, phi=-1) + alpha = cum_x1 - cum_x2 + H_S_2mu * (cum_y1-cum_y2); + } + else + { + // always 0 + alpha = 0; + } + } + break; + case Barrier.Type.UpOut: + if (type == Option.Type.Call) + { + // up-out and call + if (strike >= barrier) + { + // always 0 + alpha = 0; + } + else + { + // B1-B2+B3-B4 (eta=-1, phi=1) + alpha = cum_x1 - cum_x2 + H_S_2mu * (cum_y1-cum_y2); + } + } + else + { + // up-out and put + if (strike >= barrier) + { + // B2-B4 (eta=-1, phi=-1) + alpha = cum_x2 - H_S_2mu * cum_y2; + } + else + { + // B1-B3 (eta=-1, phi=-1) + alpha = cum_x1 - H_S_2mu * cum_y1; + } + } + break; + default: + Utils.QL_FAIL("invalid barrier type"); + break; + } + + return discount * K * alpha; + } + + private GeneralizedBlackScholesProcess process_; + private StrikedTypePayoff payoff_; + private AmericanExercise exercise_; + private BarrierOption.Arguments arguments_; + } +} diff --git a/QLNet/Pricingengines/barrier/AnalyticDoubleBarrierEngine.cs b/QLNet/Pricingengines/barrier/AnalyticDoubleBarrierEngine.cs new file mode 100644 index 000000000..020a32380 --- /dev/null +++ b/QLNet/Pricingengines/barrier/AnalyticDoubleBarrierEngine.cs @@ -0,0 +1,237 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for double barrier european options using analytical formulae + /*! The formulas are taken from "The complete guide to option pricing formulas 2nd Ed", + E.G. Haug, McGraw-Hill, p.156 and following. + Implements the Ikeda and Kunitomo series (see "Pricing Options with + Curved Boundaries" Mathematical Finance 2/1992"). + This code handles only flat barriers + + \ingroup barrierengines + + \note the formula holds only when strike is in the barrier range + + \test the correctness of the returned value is tested by + reproducing results available in literature. + */ + public class AnalyticDoubleBarrierEngine : DoubleBarrierOption.Engine + { + public AnalyticDoubleBarrierEngine(GeneralizedBlackScholesProcess process, int series = 5) + { + process_ = process; + series_ = series; + f_ = new CumulativeNormalDistribution(); + + process_.registerWith(update); + } + public override void calculate() + { + Utils.QL_REQUIRE(arguments_.exercise.type() == Exercise.Type.European,()=> + "this engine handles only european options"); + + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "non-plain payoff given"); + + double strike = payoff.strike(); + Utils.QL_REQUIRE(strike>0.0,()=> "strike must be positive"); + + double spot = underlying(); + Utils.QL_REQUIRE(spot >= 0.0,()=> "negative or null underlying given"); + Utils.QL_REQUIRE(!triggered(spot),()=> "barrier(s) already touched"); + + DoubleBarrier.Type barrierType = arguments_.barrierType; + + if (triggered(spot)) + { + if (barrierType == DoubleBarrier.Type.KnockIn) + results_.value = vanillaEquivalent(); // knocked in + else + results_.value = 0.0; // knocked out + } + else + { + switch (payoff.optionType()) + { + case Option.Type.Call: + switch (barrierType) + { + case DoubleBarrier.Type.KnockIn: + results_.value = callKI(); + break; + case DoubleBarrier.Type.KnockOut: + results_.value = callKO(); + break; + case DoubleBarrier.Type.KIKO: + case DoubleBarrier.Type.KOKI: + Utils.QL_FAIL("unsupported double-barrier type: " + barrierType); + break; + default: + Utils.QL_FAIL("unknown double-barrier type: " + barrierType); + break; + } + break; + case Option.Type.Put: + switch (barrierType) + { + case DoubleBarrier.Type.KnockIn: + results_.value = putKI(); + break; + case DoubleBarrier.Type.KnockOut: + results_.value = putKO(); + break; + case DoubleBarrier.Type.KIKO: + case DoubleBarrier.Type.KOKI: + Utils.QL_FAIL("unsupported double-barrier type: " + barrierType); + break; + default: + Utils.QL_FAIL("unknown double-barrier type: " + barrierType); + break; + } + break; + default: + Utils.QL_FAIL("unknown type"); + break; + } + } + } + + private GeneralizedBlackScholesProcess process_; + private CumulativeNormalDistribution f_ ; + private int series_; + // helper methods + private double underlying() { return process_.x0(); } + private double strike() + { + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "non-plain payoff given"); + return payoff.strike(); + } + private double residualTime() { return process_.time( arguments_.exercise.lastDate() ); } + private double volatility() { return process_.blackVolatility().link.blackVol( residualTime(), strike() ); } + private double volatilitySquared() { return volatility() * volatility(); } + private double barrierLo() { return arguments_.barrier_lo.GetValueOrDefault(); } + private double barrierHi() { return arguments_.barrier_hi.GetValueOrDefault(); } + private double rebate() { return arguments_.rebate.GetValueOrDefault(); } + private double stdDeviation() {return volatility() * Math.Sqrt(residualTime());} + private double riskFreeRate() + { + return process_.riskFreeRate().link.zeroRate( + residualTime(), Compounding.Continuous,Frequency.NoFrequency ).value(); + } + private double riskFreeDiscount() { return process_.riskFreeRate().link.discount( residualTime() ); } + private double dividendYield() + { + return process_.dividendYield().link.zeroRate( + residualTime(),Compounding.Continuous, Frequency.NoFrequency ).value(); + } + private double costOfCarry() { return riskFreeRate() - dividendYield(); } + private double dividendDiscount() { return process_.dividendYield().link.discount( residualTime() ); } + private double vanillaEquivalent() + { + // Call KI equates to vanilla - callKO + StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; + double forwardPrice = underlying() * dividendDiscount() / riskFreeDiscount(); + BlackCalculator black = new BlackCalculator(payoff, forwardPrice, stdDeviation(), riskFreeDiscount()); + double vanilla = black.value(); + if (vanilla < 0.0) + vanilla = 0.0; + return vanilla; + } + private double callKO() + { + // N.B. for flat barriers mu3=mu1 and mu2=0 + double mu1 = 2 * costOfCarry() / volatilitySquared() + 1; + double bsigma = (costOfCarry() + volatilitySquared() / 2.0) * residualTime() / stdDeviation(); + + double acc1 = 0; + double acc2 = 0; + for (int n = -series_ ; n <= series_ ; ++n) + { + double L2n = Math.Pow(barrierLo(), 2 * n); + double U2n = Math.Pow(barrierHi(), 2 * n); + double d1 = Math.Log( underlying()* U2n / (strike() * L2n) ) / stdDeviation() + bsigma; + double d2 = Math.Log( underlying()* U2n / (barrierHi() * L2n) ) / stdDeviation() + bsigma; + double d3 = Math.Log( Math.Pow(barrierLo(), 2 * n + 2) / (strike() * underlying() * U2n) ) / stdDeviation() + bsigma; + double d4 = Math.Log( Math.Pow(barrierLo(), 2 * n + 2) / (barrierHi() * underlying() * U2n) ) / stdDeviation() + bsigma; + + acc1 += Math.Pow( Math.Pow(barrierHi(), n) / Math.Pow(barrierLo(), n), mu1 ) * + (f_.value(d1) - f_.value(d2)) - + Math.Pow( Math.Pow(barrierLo(), n+1) / (Math.Pow(barrierHi(), n) * underlying()), mu1 ) * + (f_.value(d3) - f_.value(d4)); + + acc2 += Math.Pow( Math.Pow(barrierHi(), n) / Math.Pow(barrierLo(), n), mu1-2) * + (f_.value(d1 - stdDeviation()) - f_.value(d2 - stdDeviation())) - + Math.Pow( Math.Pow(barrierLo(), n+1) / (Math.Pow(barrierHi(), n) * underlying()), mu1-2 ) * + (f_.value(d3-stdDeviation()) - f_.value(d4-stdDeviation())); + } + + double rend = Math.Exp(-dividendYield() * residualTime()); + double kov = underlying() * rend * acc1 - strike() * riskFreeDiscount() * acc2; + return Math.Max(0.0, kov); + + } + + private double putKO() + { + + double mu1 = 2 * costOfCarry() / volatilitySquared() + 1; + double bsigma = (costOfCarry() + volatilitySquared() / 2.0) * residualTime() / stdDeviation(); + + double acc1 = 0; + double acc2 = 0; + for (int n = -series_ ; n <= series_ ; ++n) { + double L2n = Math.Pow(barrierLo(), 2 * n); + double U2n = Math.Pow(barrierHi(), 2 * n); + double y1 = Math.Log( underlying()* U2n / (Math.Pow(barrierLo(), 2 * n + 1)) ) / stdDeviation() + bsigma; + double y2 = Math.Log( underlying()* U2n / (strike() * L2n) ) / stdDeviation() + bsigma; + double y3 = Math.Log( Math.Pow(barrierLo(), 2 * n + 2) / (barrierLo() * underlying() * U2n) ) / stdDeviation() + bsigma; + double y4 = Math.Log( Math.Pow(barrierLo(), 2 * n + 2) / (strike() * underlying() * U2n) ) / stdDeviation() + bsigma; + + acc1 += Math.Pow( Math.Pow(barrierHi(), n) / Math.Pow(barrierLo(), n), mu1-2) * + (f_.value(y1 - stdDeviation()) - f_.value(y2 - stdDeviation())) - + Math.Pow( Math.Pow(barrierLo(), n+1) / (Math.Pow(barrierHi(), n) * underlying()), mu1-2 ) * + (f_.value(y3-stdDeviation()) - f_.value(y4-stdDeviation())); + + acc2 += Math.Pow( Math.Pow(barrierHi(), n) / Math.Pow(barrierLo(), n), mu1 ) * + (f_.value(y1) - f_.value(y2)) - + Math.Pow( Math.Pow(barrierLo(), n+1) / (Math.Pow(barrierHi(), n) * underlying()), mu1 ) * + (f_.value(y3) - f_.value(y4)); + + } + + double rend = Math.Exp(-dividendYield() * residualTime()); + double kov = strike() * riskFreeDiscount() * acc1 - underlying() * rend * acc2; + return Math.Max(0.0, kov); + + } + + private double callKI() + { + // Call KI equates to vanilla - callKO + return Math.Max(0.0, vanillaEquivalent() - callKO()); + } + + private double putKI() + { + // Put KI equates to vanilla - putKO + return Math.Max(0.0, vanillaEquivalent() - putKO()); + } + } +} diff --git a/QLNet/Pricingengines/barrier/BinomialBarrierEngine.cs b/QLNet/Pricingengines/barrier/BinomialBarrierEngine.cs new file mode 100644 index 000000000..d98053028 --- /dev/null +++ b/QLNet/Pricingengines/barrier/BinomialBarrierEngine.cs @@ -0,0 +1,182 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for barrier options using binomial trees + /*! \ingroup barrierengines + + \note Timesteps for Cox-Ross-Rubinstein trees are adjusted using Boyle and Lau algorithm. + See Journal of Derivatives, 1/1994, + "Bumping up against the barrier with the binomial method" + + \test the correctness of the returned values is tested by + checking it against analytic european results. + */ + public class BinomialBarrierEngine : BarrierOption.Engine + { + public delegate ITree GetTree( StochasticProcess1D process, double end, int steps, double strike ); + public delegate DiscretizedAsset GetAsset( BarrierOption.Arguments args, StochasticProcess process, TimeGrid grid = null ); + + /*! \param maxTimeSteps is used to limit timeSteps when using Boyle-Lau + optimization. If zero (the default) the maximum number of + steps is calculated by an heuristic: anything when < 1000, + otherwise no more than 5*timeSteps. + If maxTimeSteps is equal to timeSteps Boyle-Lau is disabled. + Likewise if the lattice is not CoxRossRubinstein Boyle-Lau is + disabled and maxTimeSteps ignored. + */ + public BinomialBarrierEngine( GetTree getTree, GetAsset getAsset , + GeneralizedBlackScholesProcess process, int timeSteps, int maxTimeSteps = 0 ) + { + process_ = process; + timeSteps_ = timeSteps; + maxTimeSteps_ = maxTimeSteps; + getTree_ = getTree; + getAsset_ = getAsset; + + Utils.QL_REQUIRE(timeSteps>0,()=> + "timeSteps must be positive, " + timeSteps + " not allowed"); + Utils.QL_REQUIRE(maxTimeSteps==0 || maxTimeSteps>=timeSteps,()=> + "maxTimeSteps must be zero or greater than or equal to timeSteps, " + maxTimeSteps + " not allowed"); + if (maxTimeSteps_== 0) + maxTimeSteps_ = Math.Max( (int)1000, timeSteps_*5); + process_.registerWith(update); + } + + public override void calculate() + { + DayCounter rfdc = process_.riskFreeRate().link.dayCounter(); + DayCounter divdc = process_.dividendYield().link.dayCounter(); + DayCounter voldc = process_.blackVolatility().link.dayCounter(); + Calendar volcal = process_.blackVolatility().link.calendar(); + + double s0 = process_.stateVariable().link.value(); + Utils.QL_REQUIRE(s0 > 0.0,()=> "negative or null underlying given"); + double v = process_.blackVolatility().link.blackVol(arguments_.exercise.lastDate(), s0); + Date maturityDate = arguments_.exercise.lastDate(); + double r = process_.riskFreeRate().link.zeroRate(maturityDate,rfdc, Compounding.Continuous, Frequency.NoFrequency).value(); + double q = process_.dividendYield().link.zeroRate(maturityDate,divdc, Compounding.Continuous, Frequency.NoFrequency).value(); + Date referenceDate = process_.riskFreeRate().link.referenceDate(); + + // binomial trees with constant coefficient + Handle flatRiskFree = new Handle(new FlatForward(referenceDate, r, rfdc)); + Handle flatDividends = new Handle(new FlatForward(referenceDate, q, divdc)); + Handle flatVol = new Handle(new BlackConstantVol(referenceDate, volcal, v, voldc)); + + StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; + Utils.QL_REQUIRE(payoff != null,()=> "non-striked payoff given"); + + double maturity = rfdc.yearFraction(referenceDate, maturityDate); + + StochasticProcess1D bs = new GeneralizedBlackScholesProcess(process_.stateVariable(), + flatDividends, flatRiskFree, flatVol); + + // correct timesteps to ensure a (local) minimum, using Boyle and Lau + // approach. See Journal of Derivatives, 1/1994, + // "Bumping up against the barrier with the binomial method" + // Note: this approach works only for CoxRossRubinstein lattices, so + // is disabled if T is not a CoxRossRubinstein or derived from it. + int optimum_steps = timeSteps_; + if ( maxTimeSteps_ > timeSteps_ && s0 > 0 && arguments_.barrier > 0 ) // boost::is_base_of::value && + { + double divisor; + if (s0 > arguments_.barrier) + divisor = Math.Pow(Math.Log(s0 / arguments_.barrier.Value), 2); + else + divisor = Math.Pow(Math.Log(arguments_.barrier.Value / s0), 2); + if (!Utils.close(divisor,0)) + { + for (int i=1; i < timeSteps_ ; ++i) + { + int optimum = (int)(( i*i * v*v * maturity) / divisor); + if (timeSteps_ < optimum) + { + optimum_steps = optimum; + break; // found first minimum with iterations>=timesteps + } + } + } + + if (optimum_steps > maxTimeSteps_) + optimum_steps = maxTimeSteps_; // too high, limit + } + + TimeGrid grid = new TimeGrid(maturity, optimum_steps); + + ITree tree = getTree_(bs, maturity, optimum_steps, payoff.strike()); + + BlackScholesLattice lattice = new BlackScholesLattice( tree, r, maturity, optimum_steps ); + + DiscretizedAsset option = getAsset_( arguments_, process_, grid ); + option.initialize(lattice, maturity); + + // Partial derivatives calculated from various points in the + // binomial tree + // (see J.C.Hull, "Options, Futures and other derivatives", 6th edition, pp 397/398) + + // Rollback to third-last step, and get underlying prices (s2) & + // option values (p2) at this point + option.rollback(grid[2]); + Vector va2 = new Vector(option.values()); + Utils.QL_REQUIRE(va2.size() == 3,()=> "Expect 3 nodes in grid at second step"); + double p2u = va2[2]; // up + double p2m = va2[1]; // mid + double p2d = va2[0]; // down (low) + double s2u = lattice.underlying(2, 2); // up price + double s2m = lattice.underlying(2, 1); // middle price + double s2d = lattice.underlying(2, 0); // down (low) price + + // calculate gamma by taking the first derivate of the two deltas + double delta2u = (p2u - p2m)/(s2u-s2m); + double delta2d = (p2m-p2d)/(s2m-s2d); + double gamma = (delta2u - delta2d) / ((s2u-s2d)/2); + + // Rollback to second-last step, and get option values (p1) at + // this point + option.rollback(grid[1]); + Vector va = new Vector(option.values()); + Utils.QL_REQUIRE(va.size() == 2,()=> "Expect 2 nodes in grid at first step"); + double p1u = va[1]; + double p1d = va[0]; + double s1u = lattice.underlying(1, 1); // up (high) price + double s1d = lattice.underlying(1, 0); // down (low) price + + double delta = (p1u - p1d) / (s1u - s1d); + + // Finally, rollback to t=0 + option.rollback(0.0); + double p0 = option.presentValue(); + + // Store results + results_.value = p0; + results_.delta = delta; + results_.gamma = gamma; + // theta can be approximated by calculating the numerical derivative + // between mid value at third-last step and at t0. The underlying price + // is the same, only time varies. + results_.theta = (p2m - p0) / grid[2]; + } + + private GeneralizedBlackScholesProcess process_; + private int timeSteps_; + private int maxTimeSteps_; + private GetTree getTree_; + private GetAsset getAsset_; + + } +} diff --git a/QLNet/Pricingengines/barrier/DiscretizedBarrierOption.cs b/QLNet/Pricingengines/barrier/DiscretizedBarrierOption.cs new file mode 100644 index 000000000..64f0eb0b1 --- /dev/null +++ b/QLNet/Pricingengines/barrier/DiscretizedBarrierOption.cs @@ -0,0 +1,243 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 DiscretizedBarrierOption : DiscretizedAsset + { + public DiscretizedBarrierOption( BarrierOption.Arguments args,StochasticProcess process,TimeGrid grid = null) + { + arguments_ = args; + vanilla_ = new DiscretizedVanillaOption(arguments_, process, grid); + + Utils.QL_REQUIRE( args.exercise.dates().Count >0 ,()=> "specify at least one stopping date" ); + + stoppingTimes_ = new InitializedList( args.exercise.dates().Count ); + for ( int i = 0; i < stoppingTimes_.Count; ++i ) + { + stoppingTimes_[i] = process.time( args.exercise.date( i ) ); + if ( grid != null && !grid.empty() ) + { + // adjust to the given grid + stoppingTimes_[i] = grid.closestTime( stoppingTimes_[i] ); + } + } + } + + public override void reset(int size) + { + vanilla_.initialize( method(), time() ); + values_ = new Vector( size, 0.0 ); + adjustValues(); + } + + public Vector vanilla() { return vanilla_.values(); } + + public BarrierOption.Arguments arguments() {return arguments_;} + + public override List mandatoryTimes() {return stoppingTimes_;} + + public void checkBarrier(Vector optvalues, Vector grid) + { + double now = time(); + bool endTime = isOnTime(stoppingTimes_.Last()); + bool stoppingTime = false; + switch (arguments_.exercise.type()) + { + case Exercise.Type.American: + if (now <= stoppingTimes_[1] && + now >= stoppingTimes_[0]) + stoppingTime = true; + break; + case Exercise.Type.European: + if (isOnTime(stoppingTimes_[0])) + stoppingTime = true; + break; + case Exercise.Type.Bermudan: + for (int i=0; i= arguments_.barrier) + { + // knocked in + if (stoppingTime) + { + optvalues[j] = Math.Max( vanilla_.values()[j], arguments_.payoff.value( grid[j] ) ); + } + else + optvalues[j] = vanilla_.values()[j]; + } + else if (endTime) + optvalues[j] = arguments_.rebate.GetValueOrDefault(); + break; + case Barrier.Type.UpOut: + if (grid[j] >= arguments_.barrier) + optvalues[j] = arguments_.rebate.GetValueOrDefault(); // knocked out + else if (stoppingTime) + optvalues[j] = Math.Max( optvalues[j], arguments_.payoff.value( grid[j] ) ); + break; + default: + Utils.QL_FAIL("invalid barrier type"); + break; + } + } + } + + protected override void postAdjustValuesImpl() + { + if (arguments_.barrierType==Barrier.Type.DownIn || + arguments_.barrierType==Barrier.Type.UpIn) + { + vanilla_.rollback(time()); + } + Vector grid = method().grid(time()); + checkBarrier(values_, grid); + } + + private BarrierOption.Arguments arguments_; + private List stoppingTimes_; + private DiscretizedVanillaOption vanilla_; + + } + + public class DiscretizedDermanKaniBarrierOption : DiscretizedAsset + { + public DiscretizedDermanKaniBarrierOption(BarrierOption.Arguments args, + StochasticProcess process,TimeGrid grid = null) + { + unenhanced_ = new DiscretizedBarrierOption(args, process, grid ); + } + + public override void reset(int size) + { + unenhanced_.initialize( method(), time() ); + values_ = new Vector( size, 0.0 ); + adjustValues(); + } + + public override List mandatoryTimes() {return unenhanced_.mandatoryTimes();} + + protected override void postAdjustValuesImpl() + { + unenhanced_.rollback( time() ); + + Vector grid = method().grid( time() ); + adjustBarrier( values_, grid ); + unenhanced_.checkBarrier( values_, grid ); // compute payoffs + } + + private void adjustBarrier(Vector optvalues, Vector grid) + { + double? barrier = unenhanced_.arguments().barrier; + double? rebate = unenhanced_.arguments().rebate; + switch (unenhanced_.arguments().barrierType) + { + case Barrier.Type.DownIn: + for (int j=0; j barrier) + { + double? ltob = (barrier-grid[j]); + double? htob = (grid[j+1]-barrier); + double htol = (grid[j+1]-grid[j]); + double u1 = unenhanced_.values()[j+1]; + double t1 = unenhanced_.vanilla()[j+1]; + optvalues[j+1] = Math.Max(0.0, (ltob.GetValueOrDefault()*t1+htob.GetValueOrDefault()*u1)/htol); + } + } + break; + case Barrier.Type.DownOut: + for (int j=0; j barrier) + { + double? a = (barrier-grid[j])*rebate; + double? b = (grid[j+1]-barrier)*unenhanced_.values()[j+1]; + double c = (grid[j+1]-grid[j]); + optvalues[j+1] = Math.Max(0.0, (a.GetValueOrDefault()+b.GetValueOrDefault())/c); + } + } + break; + case Barrier.Type.UpIn: + for (int j=0; j= barrier) + { + double? ltob = (barrier-grid[j]); + double? htob = (grid[j+1]-barrier); + double htol = (grid[j+1]-grid[j]); + double u = unenhanced_.values()[j]; + double t = unenhanced_.vanilla()[j]; + optvalues[j] = Math.Max(0.0, (ltob.GetValueOrDefault()*u+htob.GetValueOrDefault()*t)/htol); // derman std + } + } + break; + case Barrier.Type.UpOut: + for (int j=0; j= barrier) + { + double? a = (barrier-grid[j])*unenhanced_.values()[j]; + double? b = (grid[j+1]-barrier)*rebate; + double c = (grid[j+1]-grid[j]); + optvalues[j] = Math.Max(0.0, (a.GetValueOrDefault()+b.GetValueOrDefault())/c); + } + } + break; + } + } + private DiscretizedBarrierOption unenhanced_; + } +} diff --git a/QLNet/Pricingengines/barrier/VannaVolgaBarrierEngine.cs b/QLNet/Pricingengines/barrier/VannaVolgaBarrierEngine.cs new file mode 100644 index 000000000..23d0e2e31 --- /dev/null +++ b/QLNet/Pricingengines/barrier/VannaVolgaBarrierEngine.cs @@ -0,0 +1,382 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 VannaVolgaBarrierEngine : GenericEngine + { + public VannaVolgaBarrierEngine( Handle atmVol, + Handle vol25Put, + Handle vol25Call, + Handle spotFX, + Handle domesticTS, + Handle foreignTS, + bool adaptVanDelta = false, + double bsPriceWithSmile = 0.0) + { + atmVol_ = atmVol; + vol25Put_ = vol25Put; + vol25Call_ = vol25Call; + T_ = atmVol_.link.maturity(); + spotFX_ = spotFX; + domesticTS_ = domesticTS; + foreignTS_ = foreignTS; + adaptVanDelta_ = adaptVanDelta; + bsPriceWithSmile_ = bsPriceWithSmile; + + Utils.QL_REQUIRE( vol25Put_.link.delta() == -0.25,()=> "25 delta put is required by vanna volga method" ); + Utils.QL_REQUIRE( vol25Call_.link.delta() == 0.25,()=> "25 delta call is required by vanna volga method" ); + + Utils.QL_REQUIRE( vol25Put_.link.maturity() == vol25Call_.link.maturity() && + vol25Put_.link.maturity() == atmVol_.link.maturity(),()=> + "Maturity of 3 vols are not the same" ); + + Utils.QL_REQUIRE( !domesticTS_.empty(),()=> "domestic yield curve is not defined" ); + Utils.QL_REQUIRE( !foreignTS_.empty(),()=> "foreign yield curve is not defined" ); + + atmVol_.registerWith(update); + vol25Put_.registerWith(update); + vol25Call_.registerWith(update); + spotFX_.registerWith(update); + domesticTS_.registerWith(update); + foreignTS_.registerWith(update); + + } + + public override void calculate() + { + double sigmaShift_vega = 0.0001; + double sigmaShift_volga = 0.0001; + double spotShift_delta = 0.0001 * spotFX_.link.value(); + double sigmaShift_vanna = 0.0001; + + Handle x0Quote = new Handle(new SimpleQuote(spotFX_.link.value())); //used for shift + Handle atmVolQuote = new Handle(new SimpleQuote(atmVol_.link.value())); //used for shift + + BlackVolTermStructure blackVolTS = new BlackConstantVol(Settings.evaluationDate(), + new NullCalendar(), atmVolQuote, new Actual365Fixed()); + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(x0Quote,foreignTS_,domesticTS_, + new Handle(blackVolTS)); + + IPricingEngine engineBS = new AnalyticBarrierEngine(stochProcess); + + BlackDeltaCalculator blackDeltaCalculatorAtm = new BlackDeltaCalculator( + Option.Type.Call, atmVol_.link.deltaType(), x0Quote.link.value(), + domesticTS_.link.discount(T_), foreignTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_)); + double atmStrike = blackDeltaCalculatorAtm.atmStrike(atmVol_.link.atmType()); + + double call25Vol = vol25Call_.link.value(); + double put25Vol = vol25Put_.link.value(); + + BlackDeltaCalculator blackDeltaCalculatorPut25 = new BlackDeltaCalculator( + Option.Type.Put, vol25Put_.link.deltaType(), x0Quote.link.value(), + domesticTS_.link.discount(T_), foreignTS_.link.discount(T_), + put25Vol * Math.Sqrt(T_)); + double put25Strike = blackDeltaCalculatorPut25.strikeFromDelta(-0.25); + BlackDeltaCalculator blackDeltaCalculatorCall25 = new BlackDeltaCalculator( + Option.Type.Call, vol25Call_.link.deltaType(), x0Quote.link.value(), + domesticTS_.link.discount(T_), foreignTS_.link.discount(T_), + call25Vol * Math.Sqrt(T_)); + double call25Strike = blackDeltaCalculatorCall25.strikeFromDelta(0.25); + + //here use vanna volga interpolated smile to price vanilla + List strikes = new List(); + List vols = new List(); + strikes.Add(put25Strike); + vols.Add(put25Vol); + strikes.Add(atmStrike); + vols.Add(atmVol_.link.value()); + strikes.Add(call25Strike); + vols.Add(call25Vol); + VannaVolga vannaVolga = new VannaVolga(x0Quote.link.value(), domesticTS_.link.discount(T_), + foreignTS_.link.discount(T_), T_); + Interpolation interpolation = vannaVolga.interpolate(strikes, strikes.Count, vols); + interpolation.enableExtrapolation(); + StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; + double strikeVol = interpolation.value(payoff.strike()); + + //vannila option price + double vanillaOption = Utils.blackFormula(payoff.optionType(), payoff.strike(), + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + strikeVol * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + + //spot > barrier up&out 0 + if(x0Quote.link.value() >= arguments_.barrier && arguments_.barrierType == Barrier.Type.UpOut) + { + results_.value = 0.0; + results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierOutPrice"] = 0.0; + } + //spot > barrier up&in vanilla + else if(x0Quote.link.value() >= arguments_.barrier && arguments_.barrierType == Barrier.Type.UpIn) + { + results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierOutPrice"] = 0.0; + } + //spot < barrier down&out 0 + else if(x0Quote.link.value() <= arguments_.barrier && arguments_.barrierType == Barrier.Type.DownOut) + { + results_.value = 0.0; + results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierOutPrice"] = 0.0; + } + //spot < barrier down&in vanilla + else if(x0Quote.link.value() <= arguments_.barrier && arguments_.barrierType == Barrier.Type.DownIn) + { + results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierOutPrice"] = 0.0; + } + else + { + //set up BS barrier option pricing + //only calculate out barrier option price + // in barrier price = vanilla - out barrier + Barrier.Type barrierTyp; + if(arguments_.barrierType == Barrier.Type.UpOut) + barrierTyp = arguments_.barrierType; + else if(arguments_.barrierType == Barrier.Type.UpIn) + barrierTyp = Barrier.Type.UpOut; + else if(arguments_.barrierType == Barrier.Type.DownOut) + barrierTyp = arguments_.barrierType; + else + barrierTyp = Barrier.Type.DownOut; + + BarrierOption barrierOption = new BarrierOption(barrierTyp, + arguments_.barrier.GetValueOrDefault(), + arguments_.rebate.GetValueOrDefault(), + (StrikedTypePayoff)arguments_.payoff, + arguments_.exercise); + + barrierOption.setPricingEngine(engineBS); + + //BS price with atm vol + double priceBS = barrierOption.NPV(); + + double priceAtmCallBS = Utils.blackFormula(Option.Type.Call,atmStrike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + double price25CallBS = Utils.blackFormula(Option.Type.Call,call25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + double price25PutBS = Utils.blackFormula(Option.Type.Put,put25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + + //market price + double priceAtmCallMkt = Utils.blackFormula(Option.Type.Call,atmStrike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + + double price25CallMkt = Utils.blackFormula(Option.Type.Call,call25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + call25Vol * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + double price25PutMkt = Utils.blackFormula(Option.Type.Put,put25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + put25Vol * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + + + //Analytical Black Scholes formula for vanilla option + NormalDistribution norm = new NormalDistribution(); + double d1atm = (Math.Log(x0Quote.link.value()* foreignTS_.link.discount(T_)/ + domesticTS_.link.discount(T_)/atmStrike) + + 0.5*Math.Pow(atmVolQuote.link.value(),2.0) * T_)/ + (atmVolQuote.link.value() * Math.Sqrt(T_)); + double vegaAtm_Analytical = x0Quote.link.value() * norm.value(d1atm) * Math.Sqrt(T_) * + foreignTS_.link.discount(T_); + double vannaAtm_Analytical = vegaAtm_Analytical/x0Quote.link.value() * + (1.0 - d1atm/(atmVolQuote.link.value()*Math.Sqrt(T_))); + double volgaAtm_Analytical = vegaAtm_Analytical * d1atm * (d1atm - atmVolQuote.link.value() * Math.Sqrt(T_))/ + atmVolQuote.link.value(); + + double d125call = (Math.Log(x0Quote.link.value()* foreignTS_.link.discount(T_)/ + domesticTS_.link.discount(T_)/call25Strike) + + 0.5*Math.Pow(atmVolQuote.link.value(),2.0) * T_)/(atmVolQuote.link.value() * Math.Sqrt(T_)); + double vega25Call_Analytical = x0Quote.link.value() * norm.value(d125call) * Math.Sqrt(T_) * + foreignTS_.link.discount(T_); + double vanna25Call_Analytical = vega25Call_Analytical/x0Quote.link.value() * + (1.0 - d125call/(atmVolQuote.link.value()*Math.Sqrt(T_))); + double volga25Call_Analytical = vega25Call_Analytical * d125call * + (d125call - atmVolQuote.link.value() * Math.Sqrt(T_))/atmVolQuote.link.value(); + + double d125Put = (Math.Log(x0Quote.link.value()* foreignTS_.link.discount(T_)/ + domesticTS_.link.discount(T_)/put25Strike) + + 0.5*Math.Pow(atmVolQuote.link.value(),2.0) * T_)/(atmVolQuote.link.value() * Math.Sqrt(T_)); + double vega25Put_Analytical = x0Quote.link.value() * norm.value(d125Put) * Math.Sqrt(T_) * + foreignTS_.link.discount(T_); + double vanna25Put_Analytical = vega25Put_Analytical/x0Quote.link.value() * + (1.0 - d125Put/(atmVolQuote.link.value()*Math.Sqrt(T_))); + double volga25Put_Analytical = vega25Put_Analytical * d125Put * + (d125Put - atmVolQuote.link.value() * Math.Sqrt(T_))/atmVolQuote.link.value(); + + + //BS vega + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega); + barrierOption.recalculate(); + double vegaBarBS = (barrierOption.NPV() - priceBS)/sigmaShift_vega; + + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vega);//setback + + //BS volga + + //vegaBar2 + //base NPV + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_volga); + barrierOption.recalculate(); + double priceBS2 = barrierOption.NPV(); + + //shifted npv + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega); + barrierOption.recalculate(); + double vegaBarBS2 = (barrierOption.NPV() - priceBS2)/sigmaShift_vega; + double volgaBarBS = (vegaBarBS2 - vegaBarBS)/sigmaShift_volga; + + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + - sigmaShift_volga + - sigmaShift_vega);//setback + + //BS Delta + //base delta + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//shift forth + barrierOption.recalculate(); + double priceBS_delta1 = barrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back + barrierOption.recalculate(); + double priceBS_delta2 = barrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back + double deltaBar1 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta); + + //shifted delta + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vanna);//shift sigma + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//shift forth + barrierOption.recalculate(); + priceBS_delta1 = barrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back + barrierOption.recalculate(); + priceBS_delta2 = barrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back + double deltaBar2 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta); + + double vannaBarBS = (deltaBar2 - deltaBar1)/sigmaShift_vanna; + + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vanna);//set back + + //Matrix + Matrix A = new Matrix(3,3,0.0); + + //analytical + A[0,0] = vegaAtm_Analytical; + A[0,1] = vega25Call_Analytical; + A[0,2] = vega25Put_Analytical; + A[1,0] = vannaAtm_Analytical; + A[1,1] = vanna25Call_Analytical; + A[1,2] = vanna25Put_Analytical; + A[2,0] = volgaAtm_Analytical; + A[2,1] = volga25Call_Analytical; + A[2,2] = volga25Put_Analytical; + + Vector b = new Vector(3,0.0); + b[0] = vegaBarBS; + b[1] = vannaBarBS; + b[2] = volgaBarBS; + + //Vector q = inverse(A) * b; TODO implements transpose + Vector q = Matrix.inverse(A) * b; + + + //touch probability + CumulativeNormalDistribution cnd = new CumulativeNormalDistribution(); + double mu = domesticTS_.link.zeroRate(T_, Compounding.Continuous).value() - + foreignTS_.link.zeroRate(T_, Compounding.Continuous).value() - + Math.Pow(atmVol_.link.value(), 2.0)/2.0; + double h2 = (Math.Log(arguments_.barrier.GetValueOrDefault()/x0Quote.link.value()) + mu*T_)/ + (atmVol_.link.value()*Math.Sqrt(T_)); + double h2Prime = (Math.Log(x0Quote.link.value()/arguments_.barrier.GetValueOrDefault()) + mu*T_)/ + (atmVol_.link.value()*Math.Sqrt(T_)); + double probTouch = 0.0; + if(arguments_.barrierType == Barrier.Type.UpIn || arguments_.barrierType == Barrier.Type.UpOut) + probTouch = cnd.value(h2Prime) + Math.Pow(arguments_.barrier.GetValueOrDefault()/x0Quote.link.value(), + 2.0*mu/Math.Pow(atmVol_.link.value(), 2.0))*cnd.value(-h2); + else + probTouch = cnd.value(-h2Prime) + Math.Pow(arguments_.barrier.GetValueOrDefault()/x0Quote.link.value(), + 2.0*mu/Math.Pow(atmVol_.link.value(), 2.0))*cnd.value(h2); + double p_survival = 1.0 - probTouch; + + double lambda = p_survival ; + double adjust = q[0]*(priceAtmCallMkt - priceAtmCallBS) + + q[1]*(price25CallMkt - price25CallBS) + + q[2]*(price25PutMkt - price25PutBS); + double outPrice = priceBS + lambda*adjust;// + double inPrice; + + //adapt Vanilla delta + if(adaptVanDelta_ == true) + { + outPrice += lambda*(bsPriceWithSmile_ - vanillaOption); + //capfloored by (0, vanilla) + outPrice = Math.Max(0.0, Math.Min(bsPriceWithSmile_, outPrice)); + inPrice = bsPriceWithSmile_ - outPrice; + } + else + { + //capfloored by (0, vanilla) + outPrice = Math.Max(0.0, Math.Min(vanillaOption , outPrice)); + inPrice = vanillaOption - outPrice; + } + + if(arguments_.barrierType == Barrier.Type.DownOut || arguments_.barrierType == Barrier.Type.UpOut) + results_.value = outPrice; + else + results_.value = inPrice; + + results_.additionalResults["VanillaPrice"] = vanillaOption; + results_.additionalResults["BarrierInPrice"] = inPrice; + results_.additionalResults["BarrierOutPrice"] = outPrice; + results_.additionalResults["lambda"] = lambda; + } + } + + private Handle atmVol_; + private Handle vol25Put_; + private Handle vol25Call_; + private double T_; + private Handle spotFX_; + private Handle domesticTS_; + private Handle foreignTS_; + private bool adaptVanDelta_; + private double bsPriceWithSmile_; + + } +} diff --git a/QLNet/Pricingengines/barrier/VannaVolgaDoubleBarrierEngine.cs b/QLNet/Pricingengines/barrier/VannaVolgaDoubleBarrierEngine.cs new file mode 100644 index 000000000..63e86dfa4 --- /dev/null +++ b/QLNet/Pricingengines/barrier/VannaVolgaDoubleBarrierEngine.cs @@ -0,0 +1,356 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 VannaVolgaDoubleBarrierEngine : GenericEngine + { + public delegate IPricingEngine GetOriginalEngine( GeneralizedBlackScholesProcess process, int series ); + + public VannaVolgaDoubleBarrierEngine( Handle atmVol, + Handle vol25Put, + Handle vol25Call, + Handle spotFX, + Handle domesticTS, + Handle foreignTS, + GetOriginalEngine getEngine, + bool adaptVanDelta = false, + double bsPriceWithSmile = 0.0, + int series = 5 ) + : base() + + { + atmVol_ = atmVol; + vol25Put_ = vol25Put; + vol25Call_ = vol25Call; + T_ = atmVol_.link.maturity(); + spotFX_ = spotFX; + domesticTS_ = domesticTS; + foreignTS_ = foreignTS; + adaptVanDelta_ = adaptVanDelta; + bsPriceWithSmile_ = bsPriceWithSmile; + series_ = series; + getOriginalEngine_ = getEngine; + + Utils.QL_REQUIRE(vol25Put_.link.delta() == -0.25,()=> "25 delta put is required by vanna volga method"); + Utils.QL_REQUIRE(vol25Call_.link.delta() == 0.25,()=> "25 delta call is required by vanna volga method"); + + Utils.QL_REQUIRE(vol25Put_.link.maturity() == vol25Call_.link.maturity() && + vol25Put_.link.maturity() == atmVol_.link.maturity(),()=> + "Maturity of 3 vols are not the same"); + + Utils.QL_REQUIRE(!domesticTS_.empty(),()=> "domestic yield curve is not defined"); + Utils.QL_REQUIRE(!foreignTS_.empty(),()=> "foreign yield curve is not defined"); + + atmVol_.registerWith(update); + vol25Put_.registerWith(update); + vol25Call_.registerWith(update); + spotFX_.registerWith(update); + domesticTS_.registerWith(update); + foreignTS_.registerWith(update); + } + + public override void calculate() + { + double sigmaShift_vega = 0.001; + double sigmaShift_volga = 0.0001; + double spotShift_delta = 0.0001 * spotFX_.link.value(); + double sigmaShift_vanna = 0.0001; + + Utils.QL_REQUIRE(arguments_.barrierType==DoubleBarrier.Type.KnockIn || + arguments_.barrierType==DoubleBarrier.Type.KnockOut, ()=> + "Only same type barrier supported"); + + Handle x0Quote = new Handle( new SimpleQuote(spotFX_.link.value())); + Handle atmVolQuote = new Handle(new SimpleQuote(atmVol_.link.value())); + + BlackVolTermStructure blackVolTS = new BlackConstantVol( Settings.evaluationDate(), + new NullCalendar(), atmVolQuote, new Actual365Fixed()); + + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(x0Quote,foreignTS_,domesticTS_, + new Handle(blackVolTS)); + + IPricingEngine engineBS = getOriginalEngine_( stochProcess, series_ ); + + BlackDeltaCalculator blackDeltaCalculatorAtm = new BlackDeltaCalculator( + Option.Type.Call, atmVol_.link.deltaType(), x0Quote.link.value(), + domesticTS_.link.discount(T_), foreignTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_)); + + double atmStrike = blackDeltaCalculatorAtm.atmStrike(atmVol_.link.atmType()); + + double call25Vol = vol25Call_.link.value(); + double put25Vol = vol25Put_.link.value(); + BlackDeltaCalculator blackDeltaCalculatorPut25 = new BlackDeltaCalculator( + Option.Type.Put, vol25Put_.link.deltaType(), x0Quote.link.value(), + domesticTS_.link.discount(T_), foreignTS_.link.discount(T_), + put25Vol * Math.Sqrt(T_)); + double put25Strike = blackDeltaCalculatorPut25.strikeFromDelta(-0.25); + BlackDeltaCalculator blackDeltaCalculatorCall25 = new BlackDeltaCalculator( + Option.Type.Call, vol25Call_.link.deltaType(), x0Quote.link.value(), + domesticTS_.link.discount(T_), foreignTS_.link.discount(T_), + call25Vol * Math.Sqrt(T_)); + double call25Strike = blackDeltaCalculatorCall25.strikeFromDelta(0.25); + + //here use vanna volga interpolated smile to price vanilla + List strikes = new List(); + List vols = new List(); + strikes.Add(put25Strike); + vols.Add(put25Vol); + strikes.Add(atmStrike); + vols.Add(atmVol_.link.value()); + strikes.Add(call25Strike); + vols.Add(call25Vol); + VannaVolga vannaVolga = new VannaVolga(x0Quote.link.value(), foreignTS_.link.discount(T_), + foreignTS_.link.discount(T_), T_); + Interpolation interpolation = vannaVolga.interpolate(strikes, strikes.Count, vols); + interpolation.enableExtrapolation(); + StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; + Utils.QL_REQUIRE(payoff!=null, ()=> "invalid payoff"); + double strikeVol = interpolation.value(payoff.strike()); + //vannila option price + double vanillaOption = Utils.blackFormula(payoff.optionType(), payoff.strike(), + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + strikeVol * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + + //already out + if((x0Quote.link.value() > arguments_.barrier_hi || x0Quote.link.value() < arguments_.barrier_lo) + && arguments_.barrierType == DoubleBarrier.Type.KnockOut) + { + results_.value = 0.0; + results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierOutPrice"] = 0.0; + } + //already in + else if((x0Quote.link.value() > arguments_.barrier_hi || x0Quote.link.value() < arguments_.barrier_lo) + && arguments_.barrierType == DoubleBarrier.Type.KnockIn) + { + results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption; + results_.additionalResults["BarrierOutPrice"] = 0.0; + } + else + { + //set up BS barrier option pricing + //only calculate out barrier option price + // in barrier price = vanilla - out barrier + //StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; + DoubleBarrierOption doubleBarrierOption = new DoubleBarrierOption( + arguments_.barrierType, + arguments_.barrier_lo.GetValueOrDefault(), + arguments_.barrier_hi.GetValueOrDefault(), + arguments_.rebate.GetValueOrDefault(), + payoff, + arguments_.exercise); + + doubleBarrierOption.setPricingEngine(engineBS); + + //BS price + double priceBS = doubleBarrierOption.NPV(); + + double priceAtmCallBS = Utils.blackFormula(Option.Type.Call,atmStrike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + double price25CallBS = Utils.blackFormula(Option.Type.Call,call25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + double price25PutBS = Utils.blackFormula(Option.Type.Put,put25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + + //market price + double priceAtmCallMkt = Utils.blackFormula(Option.Type.Call,atmStrike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + atmVol_.link.value() * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + double price25CallMkt = Utils.blackFormula(Option.Type.Call,call25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + call25Vol * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + double price25PutMkt = Utils.blackFormula(Option.Type.Put,put25Strike, + x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_), + put25Vol * Math.Sqrt(T_), + domesticTS_.link.discount(T_)); + + //Analytical Black Scholes formula + NormalDistribution norm = new NormalDistribution(); + double d1atm = (Math.Log(x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_)/atmStrike) + + 0.5*Math.Pow(atmVolQuote.link.value(),2.0) * T_)/(atmVolQuote.link.value() * Math.Sqrt(T_)); + double vegaAtm_Analytical = x0Quote.link.value() * norm.value(d1atm) * Math.Sqrt(T_) * foreignTS_.link.discount(T_); + double vannaAtm_Analytical = vegaAtm_Analytical/x0Quote.link.value() *(1.0 - d1atm/(atmVolQuote.link.value()*Math.Sqrt(T_))); + double volgaAtm_Analytical = vegaAtm_Analytical * d1atm * (d1atm - atmVolQuote.link.value() * Math.Sqrt(T_))/atmVolQuote.link.value(); + + double d125call = (Math.Log(x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_)/call25Strike) + + 0.5*Math.Pow(atmVolQuote.link.value(),2.0) * T_)/(atmVolQuote.link.value() * Math.Sqrt(T_)); + double vega25Call_Analytical = x0Quote.link.value() * norm.value(d125call) * Math.Sqrt(T_) * foreignTS_.link.discount(T_); + double vanna25Call_Analytical = vega25Call_Analytical/x0Quote.link.value() *(1.0 - d125call/(atmVolQuote.link.value()*Math.Sqrt(T_))); + double volga25Call_Analytical = vega25Call_Analytical * d125call * (d125call - atmVolQuote.link.value() * Math.Sqrt(T_))/atmVolQuote.link.value(); + + double d125Put = (Math.Log(x0Quote.link.value()* foreignTS_.link.discount(T_)/ domesticTS_.link.discount(T_)/put25Strike) + + 0.5*Math.Pow(atmVolQuote.link.value(),2.0) * T_)/(atmVolQuote.link.value() * Math.Sqrt(T_)); + double vega25Put_Analytical = x0Quote.link.value() * norm.value(d125Put) * Math.Sqrt(T_) * foreignTS_.link.discount(T_); + double vanna25Put_Analytical = vega25Put_Analytical/x0Quote.link.value() *(1.0 - d125Put/(atmVolQuote.link.value()*Math.Sqrt(T_))); + double volga25Put_Analytical = vega25Put_Analytical * d125Put * (d125Put - atmVolQuote.link.value() * Math.Sqrt(T_))/atmVolQuote.link.value(); + + + //BS vega + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega); + doubleBarrierOption.recalculate(); + double vegaBarBS = (doubleBarrierOption.NPV() - priceBS)/sigmaShift_vega; + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vega);//setback + + //BS volga + + //vegaBar2 + //base NPV + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_volga); + doubleBarrierOption.recalculate(); + double priceBS2 = doubleBarrierOption.NPV(); + + //shifted npv + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vega); + doubleBarrierOption.recalculate(); + double vegaBarBS2 = (doubleBarrierOption.NPV() - priceBS2)/sigmaShift_vega; + double volgaBarBS = (vegaBarBS2 - vegaBarBS)/sigmaShift_volga; + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + - sigmaShift_volga + - sigmaShift_vega);//setback + + //BS Delta + //base delta + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//shift forth + doubleBarrierOption.recalculate(); + double priceBS_delta1 = doubleBarrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back + doubleBarrierOption.recalculate(); + double priceBS_delta2 = doubleBarrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back + double deltaBar1 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta); + + //shifted vanna + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() + sigmaShift_vanna);//shift sigma + //shifted delta + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//shift forth + doubleBarrierOption.recalculate(); + priceBS_delta1 = doubleBarrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() - 2 * spotShift_delta);//shift back + doubleBarrierOption.recalculate(); + priceBS_delta2 = doubleBarrierOption.NPV(); + + ((SimpleQuote)x0Quote.currentLink()).setValue(x0Quote.link.value() + spotShift_delta);//set back + double deltaBar2 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta); + + double vannaBarBS = (deltaBar2 - deltaBar1)/sigmaShift_vanna; + + ((SimpleQuote)atmVolQuote.currentLink()).setValue(atmVolQuote.link.value() - sigmaShift_vanna);//set back + + //Matrix + Matrix A = new Matrix(3,3,0.0); + + //analytical + A[0,0] = vegaAtm_Analytical; + A[0,1] = vega25Call_Analytical; + A[0,2] = vega25Put_Analytical; + A[1,0] = vannaAtm_Analytical; + A[1,1] = vanna25Call_Analytical; + A[1,2] = vanna25Put_Analytical; + A[2,0] = volgaAtm_Analytical; + A[2,1] = volga25Call_Analytical; + A[2,2] = volga25Put_Analytical; + + Vector b = new Vector(3,0.0); + b[0] = vegaBarBS; + b[1] = vannaBarBS; + b[2] = volgaBarBS; + Vector q = Matrix.inverse(A) * b; + + double H = arguments_.barrier_hi.GetValueOrDefault(); + double L = arguments_.barrier_lo.GetValueOrDefault(); + double theta_tilt_minus = ((domesticTS_.link.zeroRate(T_, Compounding.Continuous).value() - + foreignTS_.link.zeroRate(T_, Compounding.Continuous).value())/ + atmVol_.link.value() - atmVol_.link.value()/2.0)*Math.Sqrt(T_); + double h = 1.0/atmVol_.link.value() * Math.Log(H/x0Quote.link.value())/Math.Sqrt(T_); + double l = 1.0/atmVol_.link.value() * Math.Log(L/x0Quote.link.value())/Math.Sqrt(T_); + CumulativeNormalDistribution cnd = new CumulativeNormalDistribution(); + + double doubleNoTouch = 0.0; + for(int j = -series_; j< series_;j++ ) + { + double e_minus = 2*j*(h-l) - theta_tilt_minus; + doubleNoTouch += Math.Exp(-2.0*j*theta_tilt_minus*(h-l))*(cnd.value(h+e_minus) - cnd.value(l+e_minus)) + - Math.Exp(-2.0*j*theta_tilt_minus*(h-l)+2.0*theta_tilt_minus*h)* + (cnd.value(h-2.0*h+e_minus) - cnd.value(l-2.0*h+e_minus)); + } + + double p_survival = doubleNoTouch; + + double lambda = p_survival ; + double adjust = q[0]*(priceAtmCallMkt - priceAtmCallBS) + + q[1]*(price25CallMkt - price25CallBS) + + q[2]*(price25PutMkt - price25PutBS); + double outPrice = priceBS + lambda*adjust;// + double inPrice; + + //adapt Vanilla delta + if(adaptVanDelta_ == true){ + outPrice += lambda*(bsPriceWithSmile_ - vanillaOption); + //capfloored by (0, vanilla) + outPrice = Math.Max(0.0, Math.Min(bsPriceWithSmile_, outPrice)); + inPrice = bsPriceWithSmile_ - outPrice; + } + else{ + //capfloored by (0, vanilla) + outPrice = Math.Max(0.0, Math.Min(vanillaOption , outPrice)); + inPrice = vanillaOption - outPrice; + } + + if(arguments_.barrierType == DoubleBarrier.Type.KnockOut) + results_.value = outPrice; + else + results_.value = inPrice; + + results_.additionalResults["VanillaPrice"] = vanillaOption; + results_.additionalResults["BarrierInPrice"] = inPrice; + results_.additionalResults["BarrierOutPrice"] = outPrice; + results_.additionalResults["lambda"] = lambda; + + } + } + + private Handle atmVol_; + private Handle vol25Put_; + private Handle vol25Call_; + private double T_; + private Handle spotFX_; + private Handle domesticTS_; + private Handle foreignTS_; + private bool adaptVanDelta_; + private double bsPriceWithSmile_; + private int series_; + private GetOriginalEngine getOriginalEngine_; + } +} diff --git a/QLNet/Pricingengines/barrier/WulinYongDoubleBarrierEngine.cs b/QLNet/Pricingengines/barrier/WulinYongDoubleBarrierEngine.cs new file mode 100644 index 000000000..87e9304a4 --- /dev/null +++ b/QLNet/Pricingengines/barrier/WulinYongDoubleBarrierEngine.cs @@ -0,0 +1,172 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Pricing engine for barrier options using analytical formulae + /*! The formulas are taken from "Barrier Option Pricing", + Wulin Suo, Yong Wang. + + \ingroup barrierengines + + \test the correctness of the returned value is tested by + reproducing results available in literature. + */ + public class WulinYongDoubleBarrierEngine : DoubleBarrierOption.Engine + { + public WulinYongDoubleBarrierEngine(GeneralizedBlackScholesProcess process,int series = 5) + { + process_ = process; + series_ = series; + f_ = new CumulativeNormalDistribution(); + + process_.registerWith(update); + } + public override void calculate() + { + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "non-plain payoff given"); + Utils.QL_REQUIRE(payoff.strike()>0.0,()=> "strike must be positive"); + + double K = payoff.strike(); + double S = process_.x0(); + Utils.QL_REQUIRE(S >= 0.0,()=> "negative or null underlying given"); + Utils.QL_REQUIRE(!triggered(S),()=> "barrier touched"); + + DoubleBarrier.Type barrierType = arguments_.barrierType; + Utils.QL_REQUIRE(barrierType == DoubleBarrier.Type.KnockOut || + barrierType == DoubleBarrier.Type.KnockIn,()=> + "only KnockIn and KnockOut options supported"); + + double L = arguments_.barrier_lo.GetValueOrDefault(); + double H = arguments_.barrier_hi.GetValueOrDefault(); + double K_up = Math.Min(H, K); + double K_down = Math.Max(L, K); + double T = residualTime(); + double rd = riskFreeRate(); + double dd = riskFreeDiscount(); + double rf = dividendYield(); + double df = dividendDiscount(); + double vol = volatility(); + double mu = rd - rf - vol*vol/2.0; + double sgn = mu > 0 ? 1.0 :(mu < 0 ? -1.0: 0.0); + //rebate + double R_L = arguments_.rebate.GetValueOrDefault(); + double R_H = arguments_.rebate.GetValueOrDefault(); + + //european option + EuropeanOption europeanOption = new EuropeanOption(payoff, arguments_.exercise); + IPricingEngine analyticEuropeanEngine = new AnalyticEuropeanEngine(process_); + europeanOption.setPricingEngine(analyticEuropeanEngine); + double european = europeanOption.NPV(); + + double barrierOut = 0; + double rebateIn = 0; + for(int n = -series_; n < series_; n++){ + double d1 = D(S/H*Math.Pow(L/H, 2.0*n), vol*vol+mu, vol, T); + double d2 = d1 - vol*Math.Sqrt(T); + double g1 = D(H/S*Math.Pow(L/H, 2.0*n - 1.0), vol*vol+mu, vol, T); + double g2 = g1 - vol*Math.Sqrt(T); + double h1 = D(S/H*Math.Pow(L/H, 2.0*n - 1.0), vol*vol+mu, vol, T); + double h2 = h1 - vol*Math.Sqrt(T); + double k1 = D(L/S*Math.Pow(L/H, 2.0*n - 1.0), vol*vol+mu, vol, T); + double k2 = k1 - vol*Math.Sqrt(T); + double d1_down = D(S/K_down*Math.Pow(L/H, 2.0*n), vol*vol+mu, vol, T); + double d2_down = d1_down - vol*Math.Sqrt(T); + double d1_up = D(S/K_up*Math.Pow(L/H, 2.0*n), vol*vol+mu, vol, T); + double d2_up = d1_up - vol*Math.Sqrt(T); + double k1_down = D((H*H)/(K_down*S)*Math.Pow(L/H, 2.0*n), vol*vol+mu, vol, T); + double k2_down = k1_down - vol*Math.Sqrt(T); + double k1_up = D((H*H)/(K_up*S)*Math.Pow(L/H, 2.0*n), vol*vol+mu, vol, T); + double k2_up = k1_up - vol*Math.Sqrt(T); + + if( payoff.optionType() == Option.Type.Call) + { + barrierOut += Math.Pow(L/H, 2.0 * n * mu/(vol*vol))* + (df*S*Math.Pow(L/H, 2.0*n)*(f_.value(d1_down)-f_.value(d1)) + -dd*K*(f_.value(d2_down)-f_.value(d2)) + -df*Math.Pow(L/H, 2.0*n)*H*H/S*Math.Pow(H/S, 2.0*mu/(vol*vol))*(f_.value(k1_down)-f_.value(k1)) + +dd*K*Math.Pow(H/S,2.0*mu/(vol*vol))*(f_.value(k2_down)-f_.value(k2))); + } + else if(payoff.optionType() == Option.Type.Put) + { + barrierOut += Math.Pow(L/H, 2.0 * n * mu/(vol*vol))* + (dd*K*(f_.value(h2)-f_.value(d2_up)) + -df*S*Math.Pow(L/H, 2.0*n)*(f_.value(h1)-f_.value(d1_up)) + -dd*K*Math.Pow(H/S,2.0*mu/(vol*vol))*(f_.value(g2)-f_.value(k2_up)) + +df*Math.Pow(L/H, 2.0*n)*H*H/S*Math.Pow(H/S, 2.0*mu/(vol*vol))*(f_.value(g1)-f_.value(k1_up))); + } + else + { + Utils.QL_FAIL("option type not recognized"); + } + + double v1 = D(H/S*Math.Pow(H/L, 2.0*n), -mu, vol, T); + double v2 = D(H/S*Math.Pow(H/L, 2.0*n), mu, vol, T); + double v3 = D(S/L*Math.Pow(H/L, 2.0*n), -mu, vol, T); + double v4 = D(S/L*Math.Pow(H/L, 2.0*n), mu, vol, T); + rebateIn += dd * R_H * sgn * (Math.Pow(L/H, 2.0*n*mu/(vol*vol)) * f_.value(sgn * v1) - Math.Pow(H/S, 2.0*mu/(vol*vol)) * f_.value(-sgn * v2)) + + dd * R_L * sgn * (Math.Pow(L/S, 2.0*mu/(vol*vol)) * f_.value(-sgn * v3) - Math.Pow(H/L, 2.0*n*mu/(vol*vol)) * f_.value(sgn * v4)); + } + + //rebate paid at maturity + if(barrierType == DoubleBarrier.Type.KnockOut) + results_.value = barrierOut ; + else + results_.value = european - barrierOut; + + results_.additionalResults["vanilla"] = european; + results_.additionalResults["barrierOut"] = barrierOut; + results_.additionalResults["barrierIn"] = european - barrierOut; + + } + + private GeneralizedBlackScholesProcess process_; + private int series_; + private CumulativeNormalDistribution f_; + // helper methods + private double underlying() { return process_.x0(); } + private double strike() + { + PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff; + Utils.QL_REQUIRE(payoff!=null,()=> "non-plain payoff given"); + return payoff.strike(); + } + + private double residualTime() { return process_.time( arguments_.exercise.lastDate() ); } + private double volatility() { return process_.blackVolatility().link.blackVol( residualTime(), strike() ); } + //private double barrier() { return 0; } + //private double rebate() {return arguments_.rebate.GetValueOrDefault();} + //private double stdDeviation() {return volatility() * Math.Sqrt(residualTime());} + private double riskFreeRate() + { + return process_.riskFreeRate().link.zeroRate( + residualTime(), Compounding.Continuous,Frequency.NoFrequency ).value(); + } + private double riskFreeDiscount() { return process_.riskFreeRate().link.discount( residualTime() ); } + private double dividendYield() + { + return process_.dividendYield().link.zeroRate( + residualTime(),Compounding.Continuous, Frequency.NoFrequency ).value(); + } + private double dividendDiscount() { return process_.dividendYield().link.discount( residualTime() ); } + private double D(double X, double lambda, double sigma, double T) + { + return (Math.Log(X) + lambda * T)/(sigma * Math.Sqrt(T)); + } + } +} diff --git a/QLNet/Pricingengines/inflation/InflationCapFloorEngines.cs b/QLNet/Pricingengines/inflation/InflationCapFloorEngines.cs index 61b431d44..f51dff9f3 100644 --- a/QLNet/Pricingengines/inflation/InflationCapFloorEngines.cs +++ b/QLNet/Pricingengines/inflation/InflationCapFloorEngines.cs @@ -16,6 +16,7 @@ under the terms of the QLNet license. You should have received a 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 { @@ -57,9 +58,9 @@ public override void calculate() double value = 0.0; int optionlets = arguments_.startDates.Count; - InitializedList values = new InitializedList( optionlets,0.0 ); - InitializedList stdDevs = new InitializedList( optionlets,0.0); - InitializedList forwards = new InitializedList (optionlets,0.0); + List values = new InitializedList( optionlets,0.0 ); + List stdDevs = new InitializedList( optionlets,0.0); + List forwards = new InitializedList (optionlets,0.0); CapFloorType type = arguments_.type; Handle yoyTS diff --git a/QLNet/Pricingengines/inflation/InterpolatingCPICapFloorEngine.cs b/QLNet/Pricingengines/inflation/InterpolatingCPICapFloorEngine.cs new file mode 100644 index 000000000..d5da16506 --- /dev/null +++ b/QLNet/Pricingengines/inflation/InterpolatingCPICapFloorEngine.cs @@ -0,0 +1,111 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! This engine only adds timing functionality (e.g. different lag) + //! w.r.t. an existing interpolated price surface. + public class InterpolatingCPICapFloorEngine : CPICapFloor.Engine + { + public InterpolatingCPICapFloorEngine(Handle priceSurf) + { + priceSurf_ = priceSurf; + + priceSurf_.registerWith(update); + } + + public override void calculate() + { + double npv = 0.0; + + // what is the difference between the observationLag of the surface + // and the observationLag of the cap/floor? + // TODO next line will fail if units are different + Period lagDiff = arguments_.observationLag - priceSurf_.link.observationLag(); + // next line will fail if units are different if Period() is not well written + Utils.QL_REQUIRE(lagDiff >= new Period(0, TimeUnit.Months),()=> "InterpolatingCPICapFloorEngine: " + + "lag difference must be non-negative: " + lagDiff); + + // we now need an effective maturity to use in the price surface because this uses + // maturity of calibration instruments as its time axis, N.B. this must also + // use the roll because the surface does + Date effectiveMaturity = arguments_.payDate - lagDiff; + + + // what interpolation do we use? Index / flat / linear + if (arguments_.observationInterpolation == InterpolationType.AsIndex) + { + // same as index means we can just use the price surface + // since this uses the index + if (arguments_.type == Option.Type.Call) + { + npv = priceSurf_.link.capPrice(effectiveMaturity, arguments_.strike); + } + else + { + npv = priceSurf_.link.floorPrice(effectiveMaturity, arguments_.strike); + } + + + } + else + { + KeyValuePair dd = Utils.inflationPeriod(effectiveMaturity, arguments_.infIndex.link.frequency()); + double priceStart = 0.0; + + if (arguments_.type == Option.Type.Call) + { + priceStart = priceSurf_.link.capPrice(dd.Key, arguments_.strike); + } + else + { + priceStart = priceSurf_.link.floorPrice(dd.Key, arguments_.strike); + } + + // if we use a flat index vs the interpolated one ... + if (arguments_.observationInterpolation == InterpolationType.Flat) + { + // then use the price for the first day in the period because the value cannot change after then + npv = priceStart; + } + else + { + // linear interpolation will be very close + double priceEnd = 0.0; + if (arguments_.type == Option.Type.Call) + { + priceEnd = priceSurf_.link.capPrice((dd.Value + new Period(1,TimeUnit.Days)), arguments_.strike); + } + else + { + priceEnd = priceSurf_.link.floorPrice((dd.Value + new Period(1,TimeUnit.Days)), arguments_.strike); + } + + npv = priceStart + (priceEnd - priceStart) * (effectiveMaturity - dd.Key) + / ( (dd.Value + new Period(1,TimeUnit.Days)) - dd.Key); // can't get to next period' + } + } + + results_.value = npv; + } + + public virtual String name() { return "InterpolatingCPICapFloorEngine"; } + + protected Handle priceSurf_; + } +} diff --git a/QLNet/Properties/AssemblyInfo.cs b/QLNet/Properties/AssemblyInfo.cs index 37d30502c..d048384f1 100644 --- a/QLNet/Properties/AssemblyInfo.cs +++ b/QLNet/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("QLNet")] -[assembly: AssemblyCopyright("Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)")] +[assembly: AssemblyCopyright("Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -30,5 +30,5 @@ // // È possibile specificare tutti i valori o impostare come predefiniti i valori Numero revisione e Numero build // utilizzando l'asterisco (*) come descritto di seguito: -[assembly: AssemblyVersion( "1.8.0.0" )] -[assembly: AssemblyFileVersion( "1.8.0.0" )] +[assembly: AssemblyVersion( "1.9.0.0" )] +[assembly: AssemblyFileVersion( "1.9.0.0" )] diff --git a/QLNet/QLNet.csproj b/QLNet/QLNet.csproj index 1f65c7910..2f023ed66 100644 --- a/QLNet/QLNet.csproj +++ b/QLNet/QLNet.csproj @@ -87,6 +87,7 @@ + @@ -113,7 +114,10 @@ + + + @@ -127,6 +131,7 @@ + @@ -183,20 +188,29 @@ + + + + + + + + + @@ -238,6 +252,7 @@ + @@ -414,10 +429,18 @@ + + + + + + + + @@ -428,13 +451,22 @@ + + + + + + + + + @@ -492,6 +524,7 @@ + @@ -507,6 +540,7 @@ + @@ -519,11 +553,13 @@ + + @@ -535,7 +571,11 @@ + + + + diff --git a/QLNet/Quotes/DeltaVolQuote.cs b/QLNet/Quotes/DeltaVolQuote.cs new file mode 100644 index 000000000..5c59b788d --- /dev/null +++ b/QLNet/Quotes/DeltaVolQuote.cs @@ -0,0 +1,85 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + //! Class for the quotation of delta vs vol. + /*! It includes the various delta quotation types + in FX markets as well as ATM types. + */ + public class DeltaVolQuote : Quote, IObserver + { + public enum DeltaType + { + Spot, // Spot Delta, e.g. usual Black Scholes delta + Fwd, // Forward Delta + PaSpot, // Premium Adjusted Spot Delta + PaFwd // Premium Adjusted Forward Delta + } + + public enum AtmType + { + AtmNull, // Default, if not an atm quote + AtmSpot, // K=S_0 + AtmFwd, // K=F + AtmDeltaNeutral, // Call Delta = Put Delta + AtmVegaMax, // K such that Vega is Maximum + AtmGammaMax, // K such that Gamma is Maximum + AtmPutCall50 // K such that Call Delta=0.50 (only for Fwd Delta) + } + + // Standard constructor delta vs vol. + public DeltaVolQuote(double delta, Handle vol,double maturity,DeltaType deltaType) + { + delta_ = delta; + vol_ = vol; + deltaType_ = deltaType; + maturity_ = maturity; + atmType_ = DeltaVolQuote.AtmType.AtmNull; + + vol_.registerWith(update); + } + + // Additional constructor, if special atm quote is used + public DeltaVolQuote( Handle vol,DeltaType deltaType,double maturity,AtmType atmType) + { + vol_ = vol; + deltaType_ = deltaType; + maturity_ = maturity; + atmType_ = atmType; + + vol_.registerWith( update ); + } + + public void update() { notifyObservers(); } + + public override double value() { return vol_.link.value(); } + public double delta() { return delta_; } + public double maturity() { return maturity_; } + + public AtmType atmType() { return atmType_; } + public DeltaType deltaType() { return deltaType_; } + + public override bool isValid() { return !vol_.empty() && vol_.link.isValid(); } + + private double delta_; + private Handle vol_; + private DeltaType deltaType_; + private double maturity_; + private AtmType atmType_; + + } +} diff --git a/QLNet/StochasticProcess.cs b/QLNet/StochasticProcess.cs index 7befa7054..00dd166a1 100644 --- a/QLNet/StochasticProcess.cs +++ b/QLNet/StochasticProcess.cs @@ -166,7 +166,7 @@ public virtual void update() { \f] */ public abstract class StochasticProcess1D : StochasticProcess { - new protected IDiscretization1D discretization_; + protected new IDiscretization1D discretization_; protected StochasticProcess1D() {} protected StochasticProcess1D(IDiscretization1D disc) { diff --git a/QLNet/Termstructures/Inflation/CPICapFloorTermPriceSurface.cs b/QLNet/Termstructures/Inflation/CPICapFloorTermPriceSurface.cs new file mode 100644 index 000000000..cfe03bf31 --- /dev/null +++ b/QLNet/Termstructures/Inflation/CPICapFloorTermPriceSurface.cs @@ -0,0 +1,346 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Provides cpi cap/floor prices by interpolation and put/call parity (not cap/floor/swap* parity). + /*! + The inflation index MUST contain a ZeroInflationTermStructure as + this is used to create ATM. Unlike YoY price surfaces we + assume that 1) an ATM ZeroInflationTermStructure is available + and 2) that it is safe to use it. This is supported by the + fact that no stripping is required for CPI cap/floors as they + only give one flow. + + cpi cap/floors have a single (one) flow (unlike nominal + caps) because they observe cumulative inflation up to + their maturity. Options are on CPI(T)/CPI(0) but strikes + are quoted for yearly average inflation, so require transformation + via (1+quote)^T to obtain actual strikes. These are consistent + with ZCIIS quoting conventions. + + The observationLag is that for the referenced instrument prices. + Strikes are as-quoted not as-used. + */ + public abstract class CPICapFloorTermPriceSurface : InflationTermStructure + { + public CPICapFloorTermPriceSurface(double nominal, + double baseRate, // avoids an uncontrolled crash if index has no TS + Period observationLag, + Calendar cal, // calendar in index may not be useful + BusinessDayConvention bdc, + DayCounter dc, + Handle zii, + Handle yts, + List cStrikes, + List fStrikes, + List cfMaturities, + Matrix cPrice, + Matrix fPrice) + :base(0, cal, baseRate, observationLag, zii.link.frequency(), zii.link.interpolated(), yts, dc) + { + zii_ = zii; + cStrikes_ = cStrikes; + fStrikes_ = fStrikes; + cfMaturities_ = cfMaturities; + cPrice_ = cPrice; + fPrice_ = fPrice; + nominal_ = nominal; + bdc_ = bdc; + + // does the index have a TS? + Utils.QL_REQUIRE(!zii_.link.zeroInflationTermStructure().empty(),()=>"ZITS missing from index"); + Utils.QL_REQUIRE(!nominalTermStructure().empty(),()=>"nominal TS missing"); + + // data consistency checking, enough data? + Utils.QL_REQUIRE(fStrikes_.Count > 1,()=> "not enough floor strikes"); + Utils.QL_REQUIRE(cStrikes_.Count > 1,()=> "not enough cap strikes"); + Utils.QL_REQUIRE(cfMaturities_.Count > 1,()=> "not enough maturities"); + Utils.QL_REQUIRE(fStrikes_.Count == fPrice.rows(),()=> "floor strikes vs floor price rows not equal"); + Utils.QL_REQUIRE(cStrikes_.Count == cPrice.rows(),()=> "cap strikes vs cap price rows not equal"); + Utils.QL_REQUIRE(cfMaturities_.Count == fPrice.columns(),()=> "maturities vs floor price columns not equal"); + Utils.QL_REQUIRE(cfMaturities_.Count == cPrice.columns(),()=> "maturities vs cap price columns not equal"); + + // data has correct properties (positive, monotonic)? + for(int j = 0; j new Period(0,TimeUnit.Days),()=> "non-positive maturities"); + if(j>0) + { + Utils.QL_REQUIRE( cfMaturities[j] > cfMaturities[j-1],()=> "non-increasing maturities"); + } + for(int i = 0; i 0.0,()=> "non-positive floor price: " + fPrice_[i,j] ); + if(i>0) + { + Utils.QL_REQUIRE( fPrice_[i,j] >= fPrice_[i-1,j],()=> "non-increasing floor prices"); + } + } + for(int i = 0; i 0.0,()=> "non-positive cap price: " + cPrice_[i,j] ); + if(i>0) + { + Utils.QL_REQUIRE( cPrice_[i,j] <= cPrice_[i-1,j],()=> "non-decreasing cap prices: " + + cPrice_[i,j] + " then " + cPrice_[i-1,j]); + } + } + } + + + // Get the set of strikes, noting that repeats, overlaps are + // expected between caps and floors but that no overlap in the + // output is allowed so no repeats or overlaps are used + cfStrikes_ = new List(); + for(int i = 0; i maxFstrike + eps) cfStrikes_.Add(k); + } + + // final consistency checking + Utils.QL_REQUIRE(cfStrikes_.Count > 2,()=> "overall not enough strikes"); + for (int i = 1; i < cfStrikes_.Count; i++) + Utils.QL_REQUIRE( cfStrikes_[i] > cfStrikes_[i-1],()=> "cfStrikes not increasing"); + + } + //! \name InflationTermStructure interface + //@{ + public override Period observationLag() + { + return zeroInflationIndex().link.zeroInflationTermStructure().link.observationLag(); + } + public override Date baseDate() + { + return zeroInflationIndex().link.zeroInflationTermStructure().link.baseDate(); + } + //@} + + //! is based on + public Handle zeroInflationIndex() { return zii_; } + + + //! inspectors + /*! \note you don't know if price() is a cap or a floor + without checking the ZeroInflation ATM level. + */ + //@{ + public virtual double nominal() {return nominal_;} + public virtual BusinessDayConvention businessDayConvention() {return bdc_;} + //@} + + //! \warning you MUST remind the compiler in any descendants with the using:: mechanism + //! because you overload the names + //! remember that the strikes use the quoting convention + //@{ + public virtual double price( Period d, double k) + { + return this.price(cpiOptionDateFromTenor(d), k); + } + public virtual double capPrice( Period d, double k) + { + return this.capPrice(cpiOptionDateFromTenor(d), k); + } + public virtual double floorPrice( Period d, double k) + { + return this.floorPrice(cpiOptionDateFromTenor(d), k); + } + public abstract double price( Date d, double k) ; + public abstract double capPrice( Date d, double k) ; + public abstract double floorPrice( Date d, double k) ; + //@} + + public virtual List strikes() {return cfStrikes_;} + public virtual List capStrikes() {return cStrikes_;} + public virtual List floorStrikes() {return fStrikes_;} + public virtual List maturities() {return cfMaturities_;} + + public virtual Matrix capPrices() { return cPrice_; } + public virtual Matrix floorPrices() { return fPrice_; } + + public virtual double minStrike() {return cfStrikes_.First();} + public virtual double maxStrike() {return cfStrikes_.Last();} + public virtual Date minDate() {return referenceDate()+cfMaturities_.First();}// \TODO deal with index interpolation + public override Date maxDate() {return referenceDate()+cfMaturities_.Last();} + //@} + + + public virtual Date cpiOptionDateFromTenor(Period p) + { + return new Date(calendar().adjust(referenceDate() + p, businessDayConvention())); + } + + protected virtual bool checkStrike(double K) + { + return ( minStrike() <= K && K <= maxStrike() ); + } + + protected virtual bool checkMaturity(Date d) + { + return ( minDate() <= d && d <= maxDate() ); + } + + protected Handle zii_; + // data + protected List cStrikes_; + protected List fStrikes_; + protected List cfMaturities_; + protected List cfMaturityTimes_; + protected Matrix cPrice_; + protected Matrix fPrice_; + // constructed + protected List cfStrikes_; + + private double nominal_; + private BusinessDayConvention bdc_; + } + + + public class InterpolatedCPICapFloorTermPriceSurface : CPICapFloorTermPriceSurface + where Interpolator2D : IInterpolationFactory2D,new() + { + public InterpolatedCPICapFloorTermPriceSurface(double nominal, + double startRate, + Period observationLag, + Calendar cal, + BusinessDayConvention bdc, + DayCounter dc, + Handle zii, + Handle yts, + List cStrikes, + List fStrikes, + List cfMaturities, + Matrix cPrice, + Matrix fPrice) + :base(nominal, startRate, observationLag, cal, bdc, dc,zii, yts, cStrikes, fStrikes, cfMaturities, cPrice, fPrice) + { + interpolator2d_ = new Interpolator2D(); + + performCalculations(); + } + + //! \name LazyObject interface + //@{ + public override void update() { notifyObservers(); } + + //! set up the interpolations for capPrice_ and floorPrice_ + //! since we know ATM, and we have single flows, + //! we can use put/call parity to extend the surfaces + //! across all strikes + protected override void performCalculations() + { + allStrikes_ = new List(); + int nMat = cfMaturities_.Count, + ncK = cStrikes_.Count, + nfK = fStrikes_.Count, + nK = ncK + nfK; + Matrix cP = new Matrix(nK, nMat), + fP = new Matrix(nK, nMat); + Handle zts = zii_.link.zeroInflationTermStructure(); + Handle yts = this.nominalTermStructure(); + Utils.QL_REQUIRE(!zts.empty(),()=>"Zts is empty!!!"); + Utils.QL_REQUIRE(!yts.empty(),()=>"Yts is empty!!!"); + + for (int i =0; i(); + for (int i=0; i atm ? capPrice( d, k ) : floorPrice( d, k ); + } + public override double capPrice( Date d, double k) + { + double t = timeFromReference( d ); + return capPrice_.value( t, k ); + } + public override double floorPrice( Date d, double k) + { + double t = timeFromReference( d ); + return floorPrice_.value( t, k ); + } + //@} + + // data for surfaces and curve + protected List allStrikes_; + protected Matrix cPriceB_; + protected Matrix fPriceB_; + protected Interpolation2D capPrice_, floorPrice_; + protected Interpolator2D interpolator2d_; + } +} diff --git a/QLNet/Termstructures/Volatility/AtmSmileSection.cs b/QLNet/Termstructures/Volatility/AtmSmileSection.cs new file mode 100644 index 000000000..bd11b91ef --- /dev/null +++ b/QLNet/Termstructures/Volatility/AtmSmileSection.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + public class AtmSmileSection : SmileSection + { + public AtmSmileSection( SmileSection source,double? atm = null) + { + source_ = source; + f_ = atm; + if ( f_ == null ) + f_ = source_.atmLevel(); + } + + public override double minStrike() { return source_.minStrike(); } + public override double maxStrike() { return source_.maxStrike(); } + public override double? atmLevel() { return f_; } + public override Date exerciseDate() { return source_.exerciseDate(); } + public override double exerciseTime() { return source_.exerciseTime(); } + public override DayCounter dayCounter() { return source_.dayCounter(); } + public override Date referenceDate() { return source_.referenceDate(); } + public override VolatilityType volatilityType() { return source_.volatilityType();} + public override double shift() { return source_.shift(); } + + protected override double volatilityImpl(double strike) { return source_.volatility(strike);} + protected override double varianceImpl(double strike) { return source_.variance(strike);} + + private SmileSection source_; + private double? f_; + } +} diff --git a/QLNet/Termstructures/Volatility/FlatSmileSection.cs b/QLNet/Termstructures/Volatility/FlatSmileSection.cs index f27394ef8..b28907a80 100644 --- a/QLNet/Termstructures/Volatility/FlatSmileSection.cs +++ b/QLNet/Termstructures/Volatility/FlatSmileSection.cs @@ -24,7 +24,7 @@ public class FlatSmileSection : SmileSection { private double? atmLevel_; public override double? atmLevel() { - Utils.QL_REQUIRE( atmLevel_.HasValue, () => "FlatSmileSection.atmLevel is null" ); + //Utils.QL_REQUIRE( atmLevel_.HasValue, () => "FlatSmileSection.atmLevel is null" ); return atmLevel_; } diff --git a/QLNet/Termstructures/Volatility/InterpolatedSmileSection.cs b/QLNet/Termstructures/Volatility/InterpolatedSmileSection.cs index 998eac622..3dce5ea0b 100644 --- a/QLNet/Termstructures/Volatility/InterpolatedSmileSection.cs +++ b/QLNet/Termstructures/Volatility/InterpolatedSmileSection.cs @@ -23,62 +23,65 @@ namespace QLNet public class InterpolatedSmileSection : SmileSection, InterpolatedCurve where Interpolator : IInterpolationFactory, new() { - public InterpolatedSmileSection( double timeToExpiry, - List strikes, - List> stdDevHandles, - Handle atmLevel, - Interpolator interpolator = default(Interpolator), - DayCounter dc = null, //Actual365Fixed() + public InterpolatedSmileSection( double timeToExpiry, + List strikes, + List> stdDevHandles, + Handle atmLevel, + Interpolator interpolator = default(Interpolator), + DayCounter dc = null, //Actual365Fixed() + VolatilityType type = VolatilityType.ShiftedLognormal, double shift = 0.0) - : base( timeToExpiry, dc, VolatilityType.ShiftedLognormal, shift ) + : base( timeToExpiry, dc ?? new Actual365Fixed(), type, shift ) { exerciseTimeSquareRoot_ = Math.Sqrt(exerciseTime()); strikes_ = strikes; stdDevHandles_ = stdDevHandles; - atmLevel_ = atmLevel; + atmLevel_ = atmLevel; vols_ = new InitializedList(stdDevHandles.Count); - for (int i=0; i strikes, - List stdDevs, + } + + public InterpolatedSmileSection( double timeToExpiry, + List strikes, + List stdDevs, double atmLevel, - Interpolator interpolator = default(Interpolator), - DayCounter dc = null , //Actual365Fixed(), - double shift = 0.0) - :base(timeToExpiry, dc, VolatilityType.ShiftedLognormal, shift) + Interpolator interpolator = default(Interpolator), + DayCounter dc = null , //Actual365Fixed(), + VolatilityType type = VolatilityType.ShiftedLognormal, + double shift = 0.0) + :base(timeToExpiry, dc?? new Actual365Fixed(), type, shift) { exerciseTimeSquareRoot_ = Math.Sqrt(exerciseTime()); - strikes_ = strikes; + strikes_ = strikes; stdDevHandles_ = new InitializedList>(stdDevs.Count); vols_= new InitializedList(stdDevs.Count); - - // fill dummy handles to allow generic handle-based - // computations later on - for (int i=0; i(new SimpleQuote(stdDevs[i])); - - atmLevel_ = new Handle(new SimpleQuote(atmLevel)); - // check strikes!!!!!!!!!!!!!!!!!!!! + + // fill dummy handles to allow generic handle-based + // computations later on + for (int i=0; i(new SimpleQuote(stdDevs[i])); + + atmLevel_ = new Handle(new SimpleQuote(atmLevel)); + // check strikes!!!!!!!!!!!!!!!!!!!! interpolation_ = interpolator.interpolate(strikes_,strikes_.Count,vols_); - } - - public InterpolatedSmileSection( Date d, - List strikes, - List > stdDevHandles, - Handle atmLevel, + } + + public InterpolatedSmileSection( Date d, + List strikes, + List > stdDevHandles, + Handle atmLevel, DayCounter dc = null , //Actual365Fixed(), - Interpolator interpolator = default(Interpolator), - Date referenceDate = null, + Interpolator interpolator = default(Interpolator), + Date referenceDate = null, + VolatilityType type = VolatilityType.ShiftedLognormal, double shift = 0.0) - : base( d, dc, referenceDate, VolatilityType.ShiftedLognormal, shift ) + : base( d, dc?? new Actual365Fixed(), referenceDate, type, shift ) { exerciseTimeSquareRoot_ = Math.Sqrt(exerciseTime()); strikes_ = strikes; @@ -86,22 +89,22 @@ public InterpolatedSmileSection( Date d, atmLevel_ = atmLevel; vols_ = new InitializedList(stdDevHandles.Count); - for (int i=0; i strikes, - List stdDevs, - double atmLevel, + } + + public InterpolatedSmileSection( Date d, + List strikes, + List stdDevs, + double atmLevel, DayCounter dc = null , // Actual365Fixed(), - Interpolator interpolator = default(Interpolator), - Date referenceDate = null, + Interpolator interpolator = default(Interpolator), + Date referenceDate = null, double shift = 0.0) - : base( d, dc, referenceDate, VolatilityType.ShiftedLognormal, shift ) + : base( d, dc?? new Actual365Fixed(), referenceDate, VolatilityType.ShiftedLognormal, shift ) { strikes_ = strikes; stdDevHandles_ = new InitializedList>(stdDevs.Count); @@ -126,19 +129,19 @@ protected override void performCalculations() protected override double varianceImpl(double strike) { - calculate(); - double v = interpolation_.value(strike, true); + calculate(); + double v = interpolation_.value(strike, true); return v*v*exerciseTime(); } protected override double volatilityImpl(double strike) { - calculate(); + calculate(); return interpolation_.value(strike, true); - } - public override double minStrike () { return strikes_.First(); } - public override double maxStrike () { return strikes_.Last(); } - public override double? atmLevel() { return atmLevel_.link.value(); } + } + public override double minStrike () { return strikes_.First(); } + public override double maxStrike () { return strikes_.Last(); } + public override double? atmLevel() { return atmLevel_.link.value(); } public override void update() {base.update();} private double exerciseTimeSquareRoot_; diff --git a/QLNet/Termstructures/Volatility/Sabr.cs b/QLNet/Termstructures/Volatility/Sabr.cs index f35464c13..30ab33253 100644 --- a/QLNet/Termstructures/Volatility/Sabr.cs +++ b/QLNet/Termstructures/Volatility/Sabr.cs @@ -1,5 +1,6 @@ /* Copyright (C) 2008 Siarhei Novik (snovik@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -51,10 +52,7 @@ public static double unsafeSabrVolatility(double strike, double forward, double if (Math.Abs(z * z) > Const.QL_EPSILON * m) multiplier = z/xx; else { - alpha = (0.5-rho*rho)/(1.0-rho); - beta = alpha - .5; - double gamma = rho/(1-rho); - multiplier = 1.0 - beta*z + (gamma - alpha + beta*beta*.5)*z*z; + multiplier = 1.0 - 0.5 * rho * z - ( 3.0 * rho * rho - 2.0 ) * z * z / 12.0; } return (alpha/D)*multiplier*d; } diff --git a/QLNet/Termstructures/Volatility/equityfx/ImpliedVolTermStructure.cs b/QLNet/Termstructures/Volatility/equityfx/ImpliedVolTermStructure.cs new file mode 100644 index 000000000..50ca2e36b --- /dev/null +++ b/QLNet/Termstructures/Volatility/equityfx/ImpliedVolTermStructure.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + //! Implied vol term structure at a given date in the future + /*! The given date will be the implied reference date. + \note This term structure will remain linked to the original + structure, i.e., any changes in the latter will be reflected + in this structure as well. + + \warning It doesn't make financial sense to have an + asset-dependant implied Vol Term Structure. This + class should be used with term structures that are + time dependant only. + */ + public class ImpliedVolTermStructure : BlackVarianceTermStructure + { + public ImpliedVolTermStructure( Handle originalTS, Date referenceDate ) + :base(referenceDate) + { + originalTS_ = originalTS; + originalTS_.registerWith(update); + } + //! \name TermStructure interface + //@{ + public override DayCounter dayCounter() { return originalTS_.link.dayCounter(); } + public override Date maxDate() { return originalTS_.link.maxDate(); } + //@} + //! \name VolatilityTermStructure interface + //@{ + public override double minStrike() { return originalTS_.link.minStrike(); } + public override double maxStrike() { return originalTS_.link.maxStrike(); } + //@} + //! \name Visitability + //@{ + public virtual void accept( IAcyclicVisitor v ) + { + if ( v != null ) + v.visit( this ); + else + throw new Exception( "not an event visitor" ); + } + //@} + protected override double blackVarianceImpl(double t, double strike) + { + /* timeShift (and/or variance) variance at evaluation date + cannot be cached since the original curve could change + between invocations of this method */ + double timeShift = dayCounter().yearFraction( originalTS_.link.referenceDate(),referenceDate() ); + /* t is relative to the current reference date + and needs to be converted to the time relative + to the reference date of the original curve */ + return originalTS_.link.blackForwardVariance( timeShift,timeShift + t,strike,true ); + } + + private Handle originalTS_; + + } +} diff --git a/QLNet/Termstructures/Volatility/equityfx/LocalVolCurve.cs b/QLNet/Termstructures/Volatility/equityfx/LocalVolCurve.cs index 8e7e6389f..76624e6b1 100644 --- a/QLNet/Termstructures/Volatility/equityfx/LocalVolCurve.cs +++ b/QLNet/Termstructures/Volatility/equityfx/LocalVolCurve.cs @@ -1,5 +1,6 @@ /* Copyright (C) 2008 Siarhei Novik (snovik@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -16,48 +17,75 @@ under the terms of the QLNet license. You should have received a 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 { - //! Local volatility curve derived from a Black curve - public class LocalVolCurve : LocalVolTermStructure { - private Handle blackVarianceCurve_; - - public LocalVolCurve(Handle curve) - : base(curve.link.businessDayConvention(), curve.link.dayCounter()) { - blackVarianceCurve_ = curve; - - blackVarianceCurve_.registerWith(update); - } - - //! \name TermStructure interface - public override Date referenceDate() { return blackVarianceCurve_.link.referenceDate(); } - public override Calendar calendar() { return blackVarianceCurve_.link.calendar(); } - public override DayCounter dayCounter() { return blackVarianceCurve_.link.dayCounter(); } - public override Date maxDate() { return blackVarianceCurve_.link.maxDate(); } - - //! \name VolatilityTermStructure interface - public override double minStrike() { return double.MinValue; } - public override double maxStrike() { return double.MaxValue; } - - /*! The relation - \f[ - \int_0^T \sigma_L^2(t)dt = \sigma_B^2 T - \f] - holds, where \f$ \sigma_L(t) \f$ is the local volatility at - time \f$ t \f$ and \f$ \sigma_B(T) \f$ is the Black - volatility for maturity \f$ T \f$. From the above, the formula - \f[ - \sigma_L(t) = \sqrt{\frac{\mathrm{d}}{\mathrm{d}t}\sigma_B^2(t)t} - \f] - can be deduced which is here implemented. */ - protected override double localVolImpl(double t, double dummy) { - - double dt = (1.0/365.0); - double var1 = blackVarianceCurve_.link.blackVariance(t, dummy, true); - double var2 = blackVarianceCurve_.link.blackVariance(t+dt, dummy, true); - double derivative = (var2-var1)/dt; - return Math.Sqrt(derivative); - } - } +namespace QLNet +{ + //! Local volatility curve derived from a Black curve + public class LocalVolCurve : LocalVolTermStructure + { + public LocalVolCurve( Handle curve ) + : base( curve.link.businessDayConvention(), curve.link.dayCounter() ) + { + blackVarianceCurve_ = curve; + blackVarianceCurve_.registerWith( update ); + } + + //! \name TermStructure interface + public override Date referenceDate() + { + return blackVarianceCurve_.link.referenceDate(); + } + + public override Calendar calendar() + { + return blackVarianceCurve_.link.calendar(); + } + + public override DayCounter dayCounter() + { + return blackVarianceCurve_.link.dayCounter(); + } + + public override Date maxDate() + { + return blackVarianceCurve_.link.maxDate(); + } + + //! \name VolatilityTermStructure interface + public override double minStrike() + { + return double.MinValue; + } + + public override double maxStrike() + { + return double.MaxValue; + } + + /*! The relation + \f[ + \int_0^T \sigma_L^2(t)dt = \sigma_B^2 T + \f] + holds, where \f$ \sigma_L(t) \f$ is the local volatility at + time \f$ t \f$ and \f$ \sigma_B(T) \f$ is the Black + volatility for maturity \f$ T \f$. From the above, the formula + \f[ + \sigma_L(t) = \sqrt{\frac{\mathrm{d}}{\mathrm{d}t}\sigma_B^2(t)t} + \f] + can be deduced which is here implemented. */ + + protected override double localVolImpl(double t, double dummy) + { + double dt = (1.0/365.0); + double var1 = blackVarianceCurve_.link.blackVariance(t, dummy, true); + double var2 = blackVarianceCurve_.link.blackVariance(t + dt, dummy, true); + double derivative = (var2 - var1)/dt; + return Math.Sqrt(derivative); + } + + private Handle blackVarianceCurve_; + + } } diff --git a/QLNet/Termstructures/Volatility/swaption/SpreadedSwaptionVolatility.cs b/QLNet/Termstructures/Volatility/swaption/SpreadedSwaptionVolatility.cs new file mode 100644 index 000000000..d6c520597 --- /dev/null +++ b/QLNet/Termstructures/Volatility/swaption/SpreadedSwaptionVolatility.cs @@ -0,0 +1,82 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. + +namespace QLNet +{ + public class SpreadedSwaptionVolatility : SwaptionVolatilityStructure + { + public SpreadedSwaptionVolatility(Handle baseVol,Handle spread) + :base(baseVol.link.businessDayConvention(),baseVol.link.dayCounter()) + { + baseVol_ = baseVol; + spread_ = spread; + + enableExtrapolation( baseVol.link.allowsExtrapolation() ); + baseVol_.registerWith(update); + spread_.registerWith(update); + } + // All virtual methods of base classes must be forwarded + //! \name TermStructure interface + //@{ + public override DayCounter dayCounter() { return baseVol_.link.dayCounter(); } + public override Date maxDate() { return baseVol_.link.maxDate(); } + public override double maxTime() { return baseVol_.link.maxTime(); } + public override Date referenceDate() { return baseVol_.link.referenceDate(); } + public override Calendar calendar() { return baseVol_.link.calendar(); } + public override int settlementDays() { return baseVol_.link.settlementDays(); } + //@} + //! \name VolatilityTermStructure interface + //@{ + public override double minStrike() { return baseVol_.link.minStrike(); } + public override double maxStrike() { return baseVol_.link.maxStrike(); } + //@} + //! \name SwaptionVolatilityStructure interface + //@{ + public override Period maxSwapTenor() { return baseVol_.link.maxSwapTenor(); } + //@} + public override VolatilityType volatilityType() { return baseVol_.link.volatilityType(); } + + + //! \name SwaptionVolatilityStructure interface + //@{ + protected override SmileSection smileSectionImpl(Date optionDate,Period swapTenor) + { + SmileSection baseSmile = baseVol_.link.smileSection(optionDate, swapTenor, true); + return new SpreadedSmileSection(baseSmile, spread_); + } + protected override SmileSection smileSectionImpl(double optionTime,double swapLength) + { + SmileSection baseSmile = baseVol_.link.smileSection(optionTime, swapLength, true); + return new SpreadedSmileSection(baseSmile, spread_); + } + protected override double volatilityImpl(Date optionDate,Period swapTenor,double strike) + { + return baseVol_.link.volatility( optionDate, swapTenor, strike, true ) + spread_.link.value(); + } + protected override double volatilityImpl(double optionTime,double swapLength,double strike) + { + return baseVol_.link.volatility( optionTime, swapLength, strike, true ) + spread_.link.value(); + } + protected override double shiftImpl( double optionTime, double swapLength ) + { + return baseVol_.link.shift( optionTime, swapLength, true ); + } + //@} + private Handle baseVol_; + private Handle spread_; + + } +} diff --git a/QLNet/Termstructures/Volatility/swaption/SwaptionVolCube1.cs b/QLNet/Termstructures/Volatility/swaption/SwaptionVolCube1.cs new file mode 100644 index 000000000..3bff4fcbb --- /dev/null +++ b/QLNet/Termstructures/Volatility/swaption/SwaptionVolCube1.cs @@ -0,0 +1,980 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; + +/* Swaption volatility cube, fit-early-interpolate-later approach + The provided types are + SwaptionVolCube1 using the classic Hagan 2002 Sabr formula + SwaptionVolCube1a using the No Arbitrage Sabr model (Doust) +*/ + +namespace QLNet +{ + + public class SwaptionVolCube1x : SwaptionVolatilityCube + { + const double SWAPTIONVOLCUBE_VEGAWEIGHTED_TOL=15.0e-4 ; + const double SWAPTIONVOLCUBE_TOL = 100.0e-4 ; + + + public delegate SABRInterpolation GetInterpolation( GeneralizedBlackScholesProcess process ); + + public class Cube + { + public Cube() {} + public Cube(List optionDates, + List swapTenors, + List optionTimes, + List swapLengths, + int nLayers, + bool extrapolation = true, + bool backwardFlat = false) + { + optionTimes_ = new List(optionTimes); + swapLengths_ = new List(swapLengths); + optionDates_ = new List(optionDates); + swapTenors_ = new List(swapTenors); + nLayers_ = nLayers; + extrapolation_ = extrapolation; + backwardFlat_ = backwardFlat; + interpolators_ = new List(); + transposedPoints_ = new List(); + + Utils.QL_REQUIRE(optionTimes.Count>1,()=> "Cube::Cube(...): optionTimes.size()<2"); + Utils.QL_REQUIRE(swapLengths.Count>1,()=> "Cube::Cube(...): swapLengths.size()<2"); + + Utils.QL_REQUIRE(optionTimes.Count==optionDates.Count,()=>"Cube::Cube(...): optionTimes/optionDates mismatch"); + Utils.QL_REQUIRE(swapTenors.Count==swapLengths.Count,()=> "Cube::Cube(...): swapTenors/swapLengths mismatch"); + + List points = new InitializedList(nLayers_); + for (int i = 0; i < nLayers ; i++) + { + points[i] = new Matrix(optionTimes_.Count, swapLengths_.Count, 0.0); + } + + for (int k=0;k"Cube::setElement: incompatible IndexOfLayer "); + Utils.QL_REQUIRE(IndexOfRow"Cube::setElement: incompatible IndexOfRow"); + Utils.QL_REQUIRE(IndexOfColumn"Cube::setElement: incompatible IndexOfColumn"); + Matrix p = points_[IndexOfLayer]; + p[IndexOfRow,IndexOfColumn] = x; + } + public void setPoints(List x) + { + Utils.QL_REQUIRE(x.Count==nLayers_,()=>"Cube::setPoints: incompatible number of layers "); + Utils.QL_REQUIRE(x[0].rows()==optionTimes_.Count,()=>"Cube::setPoints: incompatible size 1"); + Utils.QL_REQUIRE(x[0].columns()==swapLengths_.Count,()=>"Cube::setPoints: incompatible size 2"); + + points_ = x; + } + + public void setPoint(Date optionDate,Period swapTenor,double optionTime,double swapLength,List point) + { + bool expandOptionTimes = !( optionTimes_.Exists(x => x == optionTime)); + bool expandSwapLengths = !( swapLengths_.Exists(x => x == swapLength)); + + double optionTimesPreviousNode,swapLengthsPreviousNode; + + optionTimesPreviousNode = optionTimes_.First(x => x >= optionTime); + int optionTimesIndex = optionTimes_.IndexOf(optionTimesPreviousNode); + + swapLengthsPreviousNode = swapLengths_.First(x => x >= swapLength); + int swapLengthsIndex = swapLengths_.IndexOf(swapLengthsPreviousNode); + + if (expandOptionTimes || expandSwapLengths) + expandLayers(optionTimesIndex, expandOptionTimes,swapLengthsIndex, expandSwapLengths); + + for (int k=0; k"Cube::setLayer: incompatible number of layer "); + Utils.QL_REQUIRE(x.rows()==optionTimes_.Count,()=>"Cube::setLayer: incompatible size 1"); + Utils.QL_REQUIRE(x.columns()==swapLengths_.Count,()=>"Cube::setLayer: incompatible size 2"); + + points_[i] = x; + } + + public void expandLayers(int i,bool expandOptionTimes,int j,bool expandSwapLengths) + { + Utils.QL_REQUIRE(i<=optionTimes_.Count,()=>"Cube::expandLayers: incompatible size 1"); + Utils.QL_REQUIRE(j<=swapLengths_.Count,()=>"Cube::expandLayers: incompatible size 2"); + + if (expandOptionTimes) + { + optionTimes_.Insert(i,0.0); + optionDates_.Insert(i, new Date()); + } + if (expandSwapLengths) + { + swapLengths_.Insert(j,0.0); + swapTenors_.Insert(j, new Period()); + } + + List newPoints= new InitializedList(nLayers_); + for (int ii = 0; ii < nLayers_ ; ii++) + { + newPoints[ii] = new Matrix(optionTimes_.Count, swapLengths_.Count, 0.0); + } + + for (int k=0; k=i && expandOptionTimes) indexOfRow = u+1; + for (int v=0; v=j && expandSwapLengths) indexOfCol = v+1; + Matrix p=newPoints[k],p1 = points_[k]; + p[indexOfRow,indexOfCol]=p1[u,v]; + } + } + } + setPoints(newPoints); + } + + public List optionDates() {return optionDates_;} + public List swapTenors() { return swapTenors_; } + public List optionTimes() {return optionTimes_;} + public List swapLengths() { return swapLengths_;} + public List points() { return points_;} + public List value(double optionTime,double swapLength) + { + List result = new List(); + for (int k=0; k optionTimes_, swapLengths_; + private List optionDates_; + private List swapTenors_; + private int nLayers_; + private List points_; + private List transposedPoints_; + private bool extrapolation_; + private bool backwardFlat_; + private List< Interpolation2D > interpolators_; + }; + + public SwaptionVolCube1x(Handle atmVolStructure, + List optionTenors, + List swapTenors, + List strikeSpreads, + List > > volSpreads, + SwapIndex swapIndexBase, + SwapIndex shortSwapIndexBase, + bool vegaWeightedSmileFit, + List > > parametersGuess, + List isParameterFixed, + bool isAtmCalibrated, + EndCriteria endCriteria = null, + double? maxErrorTolerance = null, + OptimizationMethod optMethod = null, + double? errorAccept = null, + bool useMaxError = false, + int maxGuesses = 50, + bool backwardFlat = false, + double cutoffStrike = 0.0001) + :base(atmVolStructure, optionTenors, swapTenors,strikeSpreads, volSpreads, swapIndexBase, + shortSwapIndexBase, vegaWeightedSmileFit) + { + parametersGuessQuotes_ = parametersGuess; + isParameterFixed_ = isParameterFixed; + isAtmCalibrated_ = isAtmCalibrated; + endCriteria_ = endCriteria; + optMethod_ = optMethod; + useMaxError_ = useMaxError; + maxGuesses_ = maxGuesses; + backwardFlat_ = backwardFlat; + cutoffStrike_ = cutoffStrike; + + // the current implementations are all lognormal, if we have + // a normal one, we can move this check to the implementing classes + Utils.QL_REQUIRE(atmVolStructure.link.volatilityType() == VolatilityType.ShiftedLognormal,()=> + "vol cubes of type 1 require a lognormal atm surface"); + + if (maxErrorTolerance != null) + { + maxErrorTolerance_ = maxErrorTolerance.Value; + } + else + { + maxErrorTolerance_ = SWAPTIONVOLCUBE_TOL; + if (vegaWeightedSmileFit_) maxErrorTolerance_ = SWAPTIONVOLCUBE_VEGAWEIGHTED_TOL; + } + if (errorAccept != null) + { + errorAccept_ = errorAccept.Value; + } + else + { + errorAccept_ = maxErrorTolerance_ / 5.0; + } + + privateObserver_ = new PrivateObserver(this); + registerWithParametersGuess(); + setParameterGuess(); + } + //! \name LazyObject interface + //@{ + protected override void performCalculations() + { + base.performCalculations(); + + //! set marketVolCube_ by volSpreads_ quotes + marketVolCube_ = new Cube(optionDates_, swapTenors_,optionTimes_, swapLengths_, nStrikes_); + double atmForward; + double atmVol, vol; + for (int j=0; j optionTimes = marketVolCube.optionTimes(); + List swapLengths = marketVolCube.swapLengths(); + List optionDates = marketVolCube.optionDates(); + List swapTenors = marketVolCube.swapTenors(); + + //List swapTenors = marketVolCube_.swapTenors(); + int k = swapTenors.IndexOf(swapTenors.First(x => x == swapTenor)); + + Utils.QL_REQUIRE(k != swapTenors.Count,()=> "swap tenor not found"); + + List calibrationResult = new InitializedList(8,0.0); + List tmpMarketVolCube = marketVolCube.points(); + + List strikes = new List(strikeSpreads_.Count); + List volatilities = new List(strikeSpreads_.Count); + + for (int j=0; j=cutoffStrike_) + { + strikes.Add(strike); + volatilities.Add(tmpMarketVolCube[i][j,k]); + } + } + + List guess = parametersGuess_.value(optionTimes[j], swapLengths[k]); + + var sabrInterpolation = new SABRInterpolation (strikes, + strikes.Count, + volatilities, + optionTimes[j], atmForward, + guess[0], guess[1], + guess[2], guess[3], + isParameterFixed_[0], + isParameterFixed_[1], + isParameterFixed_[2], + isParameterFixed_[3], + vegaWeightedSmileFit_, + endCriteria_, + optMethod_, + errorAccept_, + useMaxError_, + maxGuesses_ + );//shiftTmp + + sabrInterpolation.update(); + double interpolationError = sabrInterpolation.rmsError(); + calibrationResult[0]=sabrInterpolation.alpha(); + calibrationResult[1]=sabrInterpolation.beta(); + calibrationResult[2]=sabrInterpolation.nu(); + calibrationResult[3]=sabrInterpolation.rho(); + calibrationResult[4]=atmForward; + calibrationResult[5]=interpolationError; + calibrationResult[6]=sabrInterpolation.maxError(); + calibrationResult[7]=(double)sabrInterpolation.endCriteria(); + + Utils.QL_REQUIRE(calibrationResult[7]!=(double)EndCriteria.Type.MaxIterations,()=> + "section calibration failed: " + + "option tenor " + optionDates[j] + + ", swap tenor " + swapTenors[k] + + ": max iteration (" + + endCriteria_.maxIterations() + ")" + + ", alpha " + calibrationResult[0]+ + ", beta " + calibrationResult[1] + + ", nu " + calibrationResult[2] + + ", rho " + calibrationResult[3] + + ", max error " + calibrationResult[6] + + ", error " + calibrationResult[5] + ); + + Utils.QL_REQUIRE(useMaxError_ ? calibrationResult[6] > 0 : calibrationResult[5] < maxErrorTolerance_,()=> + "section calibration failed: "+ + "option tenor " + optionDates[j] + + ", swap tenor " + swapTenors[k] + + (useMaxError_ ? ": max error " : ": error ") + + (useMaxError_ ? calibrationResult[6] : calibrationResult[5]) + + ", alpha " + calibrationResult[0] + + ", beta " + calibrationResult[1] + + ", nu " + calibrationResult[2] + + ", rho " + calibrationResult[3] + + (useMaxError_ ? ": error" : ": max error ") + + (useMaxError_ ? calibrationResult[5] : calibrationResult[6]) + ); + + parametersCube.setPoint(optionDates[j], swapTenors[k],optionTimes[j], swapLengths[k],calibrationResult); + parametersCube.updateInterpolators(); + } + + } + public void recalibration(double beta, Period swapTenor) + { + List betaVector = new InitializedList(nOptionTenors_, beta); + recalibration(betaVector,swapTenor); + } + public void recalibration(List beta,Period swapTenor) + { + Utils.QL_REQUIRE(beta.Count == nOptionTenors_,()=> + "beta size (" + beta.Count + ") must be equal to number of option tenors (" + nOptionTenors_ + ")"); + + List swapTenors = marketVolCube_.swapTenors(); + int k = swapTenors.IndexOf(swapTenors.First(x => x == swapTenor)); + + Utils.QL_REQUIRE(k != swapTenors.Count,()=> "swap tenor (" + swapTenor + ") not found"); + + for (int i = 0; i < nOptionTenors_; ++i) + { + parametersGuess_.setElement(1, i, k, beta[i]); + } + + parametersGuess_.updateInterpolators(); + sabrCalibrationSection(marketVolCube_, sparseParameters_, swapTenor); + + volCubeAtmCalibrated_ = marketVolCube_; + if (isAtmCalibrated_) + { + fillVolatilityCube(); + sabrCalibrationSection(volCubeAtmCalibrated_, denseParameters_,swapTenor); + } + notifyObservers(); + } + public void recalibration(List swapLengths,List beta, Period swapTenor) + { + Utils.QL_REQUIRE(beta.Count == swapLengths.Count,()=> + "beta size (" + beta.Count + ") must be equal to number of swap lenghts (" + + swapLengths.Count + ")"); + + List betaTimes = new List(); + for (int i = 0; i < beta.Count; i++) + betaTimes.Add(timeFromReference(optionDateFromTenor(swapLengths[i]))); + + LinearInterpolation betaInterpolation = new LinearInterpolation(betaTimes,betaTimes.Count, beta); + + List cubeBeta = new List(); + for (int i = 0; i < optionTimes().Count; i++) + { + double t = optionTimes()[i]; + // flat extrapolation ensures admissable values + if (t < betaTimes.First()) + t = betaTimes.First(); + if (t > betaTimes.Last()) + t = betaTimes.Last(); + cubeBeta.Add(betaInterpolation.value(t)); + } + + recalibration(cubeBeta, swapTenor); + + } + public void updateAfterRecalibration() + { + volCubeAtmCalibrated_ = marketVolCube_; + if(isAtmCalibrated_) + { + fillVolatilityCube(); + denseParameters_ = sabrCalibration(volCubeAtmCalibrated_); + denseParameters_.updateInterpolators(); + } + notifyObservers(); + } + + protected void registerWithParametersGuess() + { + for (int i=0; i<4; i++) + for (int j=0; j sabrParameters = sabrParametersCube.value(optionTime, swapLength); + double shiftTmp = atmVol_.link.shift(optionTime,swapLength); + return new SabrSmileSection( optionTime, sabrParameters[4], sabrParameters ); // ,shiftTmp + } + protected Cube sabrCalibration(Cube marketVolCube) + { + List optionTimes = marketVolCube.optionTimes(); + List swapLengths = marketVolCube.swapLengths(); + List optionDates = marketVolCube.optionDates(); + List swapTenors = marketVolCube.swapTenors(); + Matrix alphas = new Matrix(optionTimes.Count, swapLengths.Count,0.0); + Matrix betas= new Matrix(alphas); + Matrix nus= new Matrix(alphas); + Matrix rhos= new Matrix(alphas); + Matrix forwards=new Matrix(alphas); + Matrix errors=new Matrix(alphas); + Matrix maxErrors=new Matrix(alphas); + Matrix endCriteria=new Matrix(alphas); + + List tmpMarketVolCube = marketVolCube.points(); + + List strikes=new InitializedList(strikeSpreads_.Count); + List volatilities=new InitializedList(strikeSpreads_.Count); + + for (int j=0; j=cutoffStrike_) + { + strikes.Add(strike); + Matrix matrix = tmpMarketVolCube[i]; + volatilities.Add(matrix[j,k]); + } + } + + List guess = parametersGuess_.value(optionTimes[j], swapLengths[k]); + + + SABRInterpolation sabrInterpolation = new SABRInterpolation(strikes, strikes.Count, + volatilities, + optionTimes[j], atmForward, + guess[0], guess[1], + guess[2], guess[3], + isParameterFixed_[0], + isParameterFixed_[1], + isParameterFixed_[2], + isParameterFixed_[3], + vegaWeightedSmileFit_, + endCriteria_, + optMethod_, + errorAccept_, + useMaxError_, + maxGuesses_ + );// shiftTmp + sabrInterpolation.update(); + + double rmsError = sabrInterpolation.rmsError(); + double maxError = sabrInterpolation.maxError(); + alphas [j,k] = sabrInterpolation.alpha(); + betas [j,k] = sabrInterpolation.beta(); + nus [j,k] = sabrInterpolation.nu(); + rhos [j,k] = sabrInterpolation.rho(); + forwards [j,k] = atmForward; + errors [j,k] = rmsError; + maxErrors [j,k] = maxError; + endCriteria[j,k] = (double)sabrInterpolation.endCriteria(); + + Utils.QL_REQUIRE(endCriteria[j,k]!=(double) EndCriteria.Type.MaxIterations,()=> + "global swaptions calibration failed: "+ + "MaxIterations reached: " + "\n" + + "option maturity = " + optionDates[j] + ", \n" + + "swap tenor = " + swapTenors[k] + ", \n" + + "error = " + (errors[j,k]) + ", \n" + + "max error = " + (maxErrors[j,k]) + ", \n" + + " alpha = " + alphas[j,k] + "n" + + " beta = " + betas[j,k] + "\n" + + " nu = " + nus[j,k] + "\n" + + " rho = " + rhos[j,k] + "\n" + ); + + Utils.QL_REQUIRE(useMaxError_ ? maxError > 0 : rmsError < maxErrorTolerance_,()=> + "global swaptions calibration failed: " + + "option tenor " + optionDates[j] + + ", swap tenor " + swapTenors[k] + + (useMaxError_ ? ": max error " : ": error") + + (useMaxError_ ? maxError : rmsError) + + " alpha = " + alphas[j,k] + "\n" + + " beta = " + betas[j,k] + "\n" + + " nu = " + nus[j,k] + "\n" + + " rho = " + rhos[j,k] + "\n" + + (useMaxError_ ? ": error" : ": max error ") + + (useMaxError_ ? rmsError :maxError) + + ); + } + } + Cube sabrParametersCube = new Cube(optionDates, swapTenors,optionTimes, swapLengths, 8,true, backwardFlat_); + sabrParametersCube.setLayer(0, alphas); + sabrParametersCube.setLayer(1, betas); + sabrParametersCube.setLayer(2, nus); + sabrParametersCube.setLayer(3, rhos); + sabrParametersCube.setLayer(4, forwards); + sabrParametersCube.setLayer(5, errors); + sabrParametersCube.setLayer(6, maxErrors); + sabrParametersCube.setLayer(7, endCriteria); + + return sabrParametersCube; + + } + protected void fillVolatilityCube() + { + SwaptionVolatilityDiscrete atmVolStructure = atmVol_.currentLink() as SwaptionVolatilityDiscrete; + + List atmOptionTimes = new List(atmVolStructure.optionTimes()); + List optionTimes = new List(volCubeAtmCalibrated_.optionTimes()); + atmOptionTimes.InsertRange(atmOptionTimes.Count,optionTimes); + atmOptionTimes.Sort(); + atmOptionTimes = atmOptionTimes.Distinct().ToList(); + + + List atmSwapLengths = new List(atmVolStructure.swapLengths()); + List swapLengths = new List(volCubeAtmCalibrated_.swapLengths()); + atmSwapLengths.InsertRange(atmSwapLengths.Count,swapLengths); + atmSwapLengths.Sort(); + atmSwapLengths = atmSwapLengths.Distinct().ToList(); + + List atmOptionDates = new List(atmVolStructure.optionDates()); + List optionDates = new List(volCubeAtmCalibrated_.optionDates()); + atmOptionDates.InsertRange(atmOptionDates.Count,optionDates); + atmOptionDates.Sort(); + atmOptionDates = atmOptionDates.Distinct().ToList(); + + List atmSwapTenors = new List(atmVolStructure.swapTenors()); + List swapTenors = new List(volCubeAtmCalibrated_.swapTenors()); + atmSwapTenors.InsertRange(atmSwapTenors.Count,swapTenors); + atmSwapTenors.Sort(); + atmSwapTenors = atmSwapTenors.Distinct().ToList(); + + createSparseSmiles(); + + for (int j=0; j x == atmOptionTimes[j])); + bool expandSwapLengths =!(swapLengths.Exists(x => x == atmSwapLengths[k])); + if(expandOptionTimes || expandSwapLengths) + { + double atmForward = atmStrike(atmOptionDates[j],atmSwapTenors[k]); + double atmVol = atmVol_.link.volatility(atmOptionDates[j], atmSwapTenors[k], atmForward); + List spreadVols = spreadVolInterpolation(atmOptionDates[j],atmSwapTenors[k]); + List volAtmCalibrated = new List(nStrikes_); + for (int i=0; i optionTimes = new List(sparseParameters_.optionTimes()); + List swapLengths = new List(sparseParameters_.swapLengths()); + if (sparseSmiles_ == null) sparseSmiles_ = new List>(); + sparseSmiles_.Clear(); + + for (int j=0; j tmp; + int n = swapLengths.Count; + tmp = new List(n); + for (int k=0; k spreadVolInterpolation(Date atmOptionDate,Period atmSwapTenor) + { + double atmOptionTime = timeFromReference(atmOptionDate); + double atmTimeLength = swapLength(atmSwapTenor); + + List result = new List(); + List optionTimes = sparseParameters_.optionTimes(); + List swapLengths = sparseParameters_.swapLengths(); + List optionDates = sparseParameters_.optionDates(); + List swapTenors = sparseParameters_.swapTenors(); + + double optionTimesPreviousNode,swapLengthsPreviousNode; + + optionTimesPreviousNode = optionTimes_.First(x => x >= atmOptionTime); + int optionTimesPreviousIndex = optionTimes_.IndexOf(optionTimesPreviousNode); + + swapLengthsPreviousNode = swapLengths_.First(x => x >= atmTimeLength); + int swapLengthsPreviousIndex = swapLengths_.IndexOf(swapLengthsPreviousNode); + + if (optionTimesPreviousIndex >0) + optionTimesPreviousIndex --; + + if (swapLengthsPreviousIndex >0) + swapLengthsPreviousIndex --; + + List< List > smiles = new List>(); + List smilesOnPreviousExpiry = new List(); + List smilesOnNextExpiry = new List(); + + Utils.QL_REQUIRE(optionTimesPreviousIndex+1 < sparseSmiles_.Count,()=> + "optionTimesPreviousIndex+1 >= sparseSmiles_.size()"); + Utils.QL_REQUIRE(swapLengthsPreviousIndex+1 < sparseSmiles_[0].Count,()=> + "swapLengthsPreviousIndex+1 >= sparseSmiles_[0].size()"); + smilesOnPreviousExpiry.Add(sparseSmiles_[optionTimesPreviousIndex][swapLengthsPreviousIndex]); + smilesOnPreviousExpiry.Add(sparseSmiles_[optionTimesPreviousIndex][swapLengthsPreviousIndex+1]); + smilesOnNextExpiry.Add(sparseSmiles_[optionTimesPreviousIndex+1][swapLengthsPreviousIndex]); + smilesOnNextExpiry.Add(sparseSmiles_[optionTimesPreviousIndex+1][swapLengthsPreviousIndex+1]); + + smiles.Add(smilesOnPreviousExpiry); + smiles.Add(smilesOnNextExpiry); + + List optionsNodes = new InitializedList(2); + optionsNodes[0] = optionTimes[optionTimesPreviousIndex]; + optionsNodes[1] = optionTimes[optionTimesPreviousIndex+1]; + + List optionsDateNodes = new InitializedList(2); + optionsDateNodes[0] = optionDates[optionTimesPreviousIndex]; + optionsDateNodes[1] = optionDates[optionTimesPreviousIndex+1]; + + List swapLengthsNodes = new InitializedList(2); + swapLengthsNodes[0] = swapLengths[swapLengthsPreviousIndex]; + swapLengthsNodes[1] = swapLengths[swapLengthsPreviousIndex+1]; + + List swapTenorNodes = new InitializedList(2); + swapTenorNodes[0] = swapTenors[swapLengthsPreviousIndex]; + swapTenorNodes[1] = swapTenors[swapLengthsPreviousIndex+1]; + + double atmForward = atmStrike(atmOptionDate, atmSwapTenor); + double shift = atmVol_.link.shift(atmOptionTime, atmTimeLength); + + Matrix atmForwards = new Matrix(2, 2, 0.0); + Matrix atmShifts = new Matrix(2,2,0.0); + Matrix atmVols = new Matrix(2, 2, 0.0); + for (int i=0; i<2; i++) + { + for (int j=0; j<2; j++) + { + atmForwards[i,j] = atmStrike(optionsDateNodes[i],swapTenorNodes[j]); + atmShifts[i,j] = atmVol_.link.shift(optionsNodes[i], swapLengthsNodes[j]); + atmVols[i,j] = atmVol_.link.volatility(optionsDateNodes[i], swapTenorNodes[j], atmForwards[i,j]); + /* With the old implementation the interpolated spreads on ATM + volatilities were null even if the spreads on ATM volatilities to be + interpolated were non-zero. The new implementation removes + this behaviour, but introduces a small ERROR in the cube: + even if no spreads are applied on any cube ATM volatility corresponding + to quoted smile sections (that is ATM volatilities in sparse cube), the + cube ATM volatilities corresponding to not quoted smile sections (that + is ATM volatilities in dense cube) are no more exactly the quoted values, + but that ones PLUS the linear interpolation of the fit errors on the ATM + volatilities in sparse cube whose spreads are used in the calculation. + A similar imprecision is introduced to the volatilities in dense cube + whith moneyness near to 1. + (See below how spreadVols are calculated). + The extent of this error depends on the quality of the fit: in case of + good fits it is negligibile. + */ + } + + } + + for (int k=0; k > sparseSmiles_; + private List > > parametersGuessQuotes_; + private Cube parametersGuess_; + private List isParameterFixed_; + private bool isAtmCalibrated_; + private EndCriteria endCriteria_; + private double maxErrorTolerance_; + private OptimizationMethod optMethod_; + private double errorAccept_; + private bool useMaxError_; + private int maxGuesses_; + private bool backwardFlat_; + private double cutoffStrike_; + + class PrivateObserver : IObserver + { + public PrivateObserver(SwaptionVolCube1x v) + { + v_ = v; + } + public void update() + { + v_.setParameterGuess(); + v_.update(); + } + private SwaptionVolCube1x v_; + } + + PrivateObserver privateObserver_; + } + + //public class SwaptionVolCubeSabrModel + //{ + // public SwaptionVolCubeSabrModel() + // { + // Interpolation = new SABRInterpolation(); + // } + // SABRInterpolation Interpolation { get; set; } + // SabrSmileSection SmileSection { get; set; } + //} + //public class SwaptionVolCube1 : SwaptionVolCube1x + //{ + + //} +} diff --git a/QLNet/Termstructures/Volatility/swaption/SwaptionVolCube2.cs b/QLNet/Termstructures/Volatility/swaption/SwaptionVolCube2.cs new file mode 100644 index 000000000..fe76faab0 --- /dev/null +++ b/QLNet/Termstructures/Volatility/swaption/SwaptionVolCube2.cs @@ -0,0 +1,115 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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 +{ + /*! The swaption vol cube is made up of ordered swaption vol surface + layers, each layer referring to a swap index of a given length + (in years), all indexes belonging to the same family. In order + to identify the family (and its market conventions) an index of + whatever length from that family must be passed in as + swapIndexBase. + + Often for short swap length the swap index family is different, + e.g. the EUR case: swap vs 6M Euribor is used for length>1Y, + while swap vs 3M Euribor is used for the 1Y length. The + shortSwapIndexBase is used to identify this second family. + */ + public class SwaptionVolCube2 : SwaptionVolatilityCube + { + public SwaptionVolCube2( Handle atmVolStructure, + List optionTenors, + List swapTenors, + List strikeSpreads, + List > > volSpreads, + SwapIndex swapIndexBase, + SwapIndex shortSwapIndexBase, + bool vegaWeightedSmileFit) + :base(atmVolStructure, optionTenors, swapTenors,strikeSpreads, volSpreads, swapIndexBase, + shortSwapIndexBase,vegaWeightedSmileFit) + { + volSpreadsInterpolator_= new List(); + volSpreadsMatrix_ = new List(nStrikes_); + for ( int i = 0 ; i < nStrikes_; i++) + volSpreadsMatrix_.Add (new Matrix(optionTenors.Count, swapTenors.Count, 0.0)); + } + //! \name LazyObject interface + //@{ + protected override void performCalculations() + { + base.performCalculations(); + //! set volSpreadsMatrix_ by volSpreads_ quotes + for ( int i = 0; i < nStrikes_; i++ ) + for ( int j = 0; j < nOptionTenors_; j++ ) + for ( int k = 0; k < nSwapTenors_; k++ ) + { + Matrix p = volSpreadsMatrix_[i]; + p[j,k] = volSpreads_[j * nSwapTenors_ + k][i].link.value(); + } + //! create volSpreadsInterpolator_ + for (int i=0; i strikes, stdDevs; + strikes=new List(nStrikes_); + stdDevs=new List(nStrikes_); + double length = swapLength(swapTenor); + for (int i=0; i(optionTime,strikes,stdDevs,atmForward,new Linear(), + new Actual365Fixed(), volatilityType(),shift); + } + + protected override SmileSection smileSectionImpl( double optionTime, double swapLength) + { + calculate(); + Date optionDate = optionDateFromTime(optionTime); + Rounding rounder = new Rounding(0); + Period swapTenor = new Period((int)(rounder.Round(swapLength*12.0)), TimeUnit.Months); + // ensure that option date is valid fixing date + optionDate = + swapTenor > shortSwapIndexBase_.tenor() + ? swapIndexBase_.fixingCalendar().adjust(optionDate, BusinessDayConvention.Following) + : shortSwapIndexBase_.fixingCalendar().adjust(optionDate,BusinessDayConvention.Following); + return smileSectionImpl(optionDate, swapTenor); + } + //@} + private List volSpreadsInterpolator_; + private List volSpreadsMatrix_; + } +} diff --git a/QLNet/Termstructures/Volatility/swaption/SwaptionVolatilityCube.cs b/QLNet/Termstructures/Volatility/swaption/SwaptionVolatilityCube.cs new file mode 100644 index 000000000..db8a93d64 --- /dev/null +++ b/QLNet/Termstructures/Volatility/swaption/SwaptionVolatilityCube.cs @@ -0,0 +1,218 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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.Collections.Generic; + +namespace QLNet +{ + public abstract class SwaptionVolatilityCube : SwaptionVolatilityDiscrete + { + public SwaptionVolatilityCube( Handle atmVol, + List optionTenors, + List swapTenors, + List strikeSpreads, + List > > volSpreads, + SwapIndex swapIndexBase, + SwapIndex shortSwapIndexBase, + bool vegaWeightedSmileFit) + :base(optionTenors, swapTenors, 0,atmVol.link.calendar(),atmVol.link.businessDayConvention(), + atmVol.link.dayCounter()) + { + atmVol_ = atmVol; + nStrikes_ = strikeSpreads.Count; + strikeSpreads_ = strikeSpreads; + localStrikes_ = new InitializedList(nStrikes_); + localSmile_ = new List(nStrikes_); + volSpreads_ = volSpreads; + swapIndexBase_ = swapIndexBase; + shortSwapIndexBase_ = shortSwapIndexBase; + vegaWeightedSmileFit_ = vegaWeightedSmileFit; + + Utils.QL_REQUIRE(!atmVol_.empty(),()=> "atm vol handle not linked to anything"); + for (int i=1; i + "non increasing strike spreads: " +i + " is " + strikeSpreads_[i-1] + ", " + + (i+1) + " is " + strikeSpreads_[i]); + + Utils.QL_REQUIRE(!volSpreads_.empty(),()=> "empty vol spreads matrix"); + + Utils.QL_REQUIRE(nOptionTenors_*nSwapTenors_==volSpreads_.Count,()=> + "mismatch between number of option tenors * swap tenors (" + + nOptionTenors_*nSwapTenors_ + ") and number of rows (" + + volSpreads_.Count + ")"); + + for (int i=0; i + "mismatch between number of strikes (" + nStrikes_ + + ") and number of columns (" + volSpreads_[i].Count + + ") in the " + (i+1) + " row"); + + atmVol_.registerWith(update); + atmVol_.link.enableExtrapolation(); + + swapIndexBase_.registerWith(update); + shortSwapIndexBase_.registerWith(update); + + Utils.QL_REQUIRE(shortSwapIndexBase_.tenor() + "short index tenor (" + shortSwapIndexBase_.tenor() + + ") is not less than index tenor (" + + swapIndexBase_.tenor() + ")"); + + registerWithVolatilitySpread(); + Settings.registerWith(update); + evaluationDate_ = Settings.evaluationDate(); + } + //! \name TermStructure interface + //@{ + public new DayCounter dayCounter() { return atmVol_.link.dayCounter(); } + public override Date maxDate() { return atmVol_.link.maxDate(); } + public new double maxTime() { return atmVol_.link.maxTime(); } + public override Date referenceDate() + { + if (atmVol_ == null) + return base.referenceDate(); + + return atmVol_.link.referenceDate(); + } + public new Calendar calendar() { return atmVol_.link.calendar(); } + public new int settlementDays() { return atmVol_.link.settlementDays(); } + //! \name VolatilityTermStructure interface + //@{ + public override double minStrike() { return -double.MaxValue; } + public override double maxStrike() { return double.MaxValue; } + //@} + //! \name SwaptionVolatilityStructure interface + //@{ + public override Period maxSwapTenor() { return atmVol_.link.maxSwapTenor(); } + //@} + //! \name Other inspectors + //@{ + public double atmStrike( Date optionDate, Period swapTenor) + { + // FIXME use a familyName-based index factory + if ( swapTenor > shortSwapIndexBase_.tenor() ) + { + if ( swapIndexBase_.exogenousDiscount() ) + { + return new SwapIndex( swapIndexBase_.familyName(), + swapTenor, + swapIndexBase_.fixingDays(), + swapIndexBase_.currency(), + swapIndexBase_.fixingCalendar(), + swapIndexBase_.fixedLegTenor(), + swapIndexBase_.fixedLegConvention(), + swapIndexBase_.dayCounter(), + swapIndexBase_.iborIndex(), + swapIndexBase_.discountingTermStructure() ).fixing( optionDate ); + } + else + { + return new SwapIndex( swapIndexBase_.familyName(), + swapTenor, + swapIndexBase_.fixingDays(), + swapIndexBase_.currency(), + swapIndexBase_.fixingCalendar(), + swapIndexBase_.fixedLegTenor(), + swapIndexBase_.fixedLegConvention(), + swapIndexBase_.dayCounter(), + swapIndexBase_.iborIndex() ).fixing( optionDate ); + } + } + else + { + if ( shortSwapIndexBase_.exogenousDiscount() ) + { + return new SwapIndex( shortSwapIndexBase_.familyName(), + swapTenor, + shortSwapIndexBase_.fixingDays(), + shortSwapIndexBase_.currency(), + shortSwapIndexBase_.fixingCalendar(), + shortSwapIndexBase_.fixedLegTenor(), + shortSwapIndexBase_.fixedLegConvention(), + shortSwapIndexBase_.dayCounter(), + shortSwapIndexBase_.iborIndex(), + shortSwapIndexBase_.discountingTermStructure() ).fixing( optionDate ); + } + else + { + return new SwapIndex( shortSwapIndexBase_.familyName(), + swapTenor, + shortSwapIndexBase_.fixingDays(), + shortSwapIndexBase_.currency(), + shortSwapIndexBase_.fixingCalendar(), + shortSwapIndexBase_.fixedLegTenor(), + shortSwapIndexBase_.fixedLegConvention(), + shortSwapIndexBase_.dayCounter(), + shortSwapIndexBase_.iborIndex() ).fixing( optionDate ); + } + } + } + public double atmStrike( Period optionTenor, Period swapTenor) + { + Date optionDate = optionDateFromTenor(optionTenor); + return atmStrike(optionDate, swapTenor); + } + public Handle atmVol() { return atmVol_; } + public List strikeSpreads() { return strikeSpreads_; } + public List > > volSpreads() { return volSpreads_; } + public SwapIndex swapIndexBase() { return swapIndexBase_; } + public SwapIndex shortSwapIndexBase() { return shortSwapIndexBase_; } + public bool vegaWeightedSmileFit() { return vegaWeightedSmileFit_; } + //@} + //! \name LazyObject interface + //@{ + protected override void performCalculations() + { + Utils.QL_REQUIRE(nStrikes_ >= requiredNumberOfStrikes(),()=> + "too few strikes (" + nStrikes_ + + ") required are at least " + + requiredNumberOfStrikes()); + base.performCalculations(); + } + //@} + public override VolatilityType volatilityType() { return atmVol_.link.volatilityType(); } + + protected void registerWithVolatilitySpread() + { + for ( int i = 0; i < nStrikes_; i++ ) + for ( int j = 0; j < nOptionTenors_; j++ ) + for ( int k = 0; k < nSwapTenors_; k++ ) + volSpreads_[j * nSwapTenors_ + k][i].registerWith(update); + } + protected virtual int requiredNumberOfStrikes() { return 2; } + protected override double volatilityImpl(double optionTime,double swapLength,double strike) + { + return smileSectionImpl(optionTime, swapLength).volatility(strike); + } + protected override double volatilityImpl(Date optionDate,Period swapTenor,double strike) + { + return smileSectionImpl( optionDate, swapTenor ).volatility( strike ); + } + protected override double shiftImpl(double optionTime, double swapLength) + { + return atmVol_.link.shift( optionTime, swapLength ); + } + protected Handle atmVol_; + protected int nStrikes_; + protected List strikeSpreads_; + protected List localStrikes_; + protected List localSmile_; + protected List > > volSpreads_; + protected SwapIndex swapIndexBase_, shortSwapIndexBase_; + protected bool vegaWeightedSmileFit_; + + } +} diff --git a/QLNet/Termstructures/Volatility/swaption/SwaptionVolatilityStructure.cs b/QLNet/Termstructures/Volatility/swaption/SwaptionVolatilityStructure.cs index 368e553b9..99e0d276d 100644 --- a/QLNet/Termstructures/Volatility/swaption/SwaptionVolatilityStructure.cs +++ b/QLNet/Termstructures/Volatility/swaption/SwaptionVolatilityStructure.cs @@ -147,6 +147,52 @@ public double blackVariance(double optionTime, double swapLength, double strike, return v * v * optionTime; } + + //! returns the shift for a given option tenor and swap tenor + public double shift( Period optionTenor, Period swapTenor,bool extrapolate = false) + { + Date optionDate = optionDateFromTenor( optionTenor ); + return shift( optionDate, swapTenor, extrapolate ); + } + //! returns the shift for a given option date and swap tenor + public double shift( Date optionDate, Period swapTenor,bool extrapolate = false) + { + checkSwapTenor( swapTenor, extrapolate ); + checkRange( optionDate, extrapolate ); + return shiftImpl( optionDate, swapTenor ); + } + //! returns the shift for a given option time and swap tenor + public double shift( double optionTime, Period swapTenor,bool extrapolate = false) + { + checkSwapTenor( swapTenor, extrapolate ); + checkRange( optionTime, extrapolate ); + double length = swapLength( swapTenor ); + return shiftImpl( optionTime, length ); + } + //! returns the shift for a given option tenor and swap length + public double shift( Period optionTenor, double swapLength, bool extrapolate = false) + { + Date optionDate = optionDateFromTenor( optionTenor ); + return shift( optionDate, swapLength, extrapolate ); + } + //! returns the shift for a given option date and swap length + public double shift( Date optionDate, double swapLength, bool extrapolate = false) + { + checkSwapTenor( swapLength, extrapolate ); + checkRange( optionDate, extrapolate ); + double optionTime = timeFromReference( optionDate ); + return shiftImpl( optionTime, swapLength ); + } + //! returns the shift for a given option time and swap length + public double shift( double optionTime, double swapLength,bool extrapolate = false) + { + checkSwapTenor( swapLength, extrapolate ); + checkRange( optionTime, extrapolate ); + return shiftImpl( optionTime, swapLength ); + } + + + //! returns the smile for a given option tenor and swap tenor public SmileSection smileSection(Period optionTenor, Period swapTenor, bool extr = false) { @@ -192,6 +238,9 @@ public SmileSection smileSection(double optionTime, double swapLength, bool extr #endregion + //! volatility type + public virtual VolatilityType volatilityType() {return VolatilityType.ShiftedLognormal;} + //! implements the conversion between swap tenor and swap (time) length public double swapLength(Period swapTenor) { @@ -232,6 +281,17 @@ protected virtual double volatilityImpl(Date optionDate, Period swapTenor, doubl protected abstract double volatilityImpl(double optionTime, double swapLength, double strike); + protected virtual double shiftImpl( Date optionDate, Period swapTenor) + { + return shiftImpl( timeFromReference( optionDate ), swapLength( swapTenor ) ); + } + protected virtual double shiftImpl( double optionTime, double swapLength) + { + Utils.QL_REQUIRE(volatilityType() == VolatilityType.ShiftedLognormal,()=> + "shift parameter only makes sense for lognormal volatilities" ); + return 0.0; + } + protected void checkSwapTenor(Period swapTenor, bool extrapolate) { Utils.QL_REQUIRE( swapTenor.length() > 0, () => "non-positive swap tenor (" + swapTenor + ") given" ); diff --git a/QLNet/Termstructures/Volatility/swaption/swaptionvoldiscrete.cs b/QLNet/Termstructures/Volatility/swaption/swaptionvoldiscrete.cs index 0af328ae6..8604d263c 100644 --- a/QLNet/Termstructures/Volatility/swaption/swaptionvoldiscrete.cs +++ b/QLNet/Termstructures/Volatility/swaption/swaptionvoldiscrete.cs @@ -169,9 +169,15 @@ protected override void performCalculations(){ // check if date recalculation could be avoided here if (moving_){ initializeOptionDatesAndTimes(); - initializeSwapLengths(); + initializeSwapLengths(); + optionInterpolator_.update(); } } + //! additional inspectors + public Date optionDateFromTime(double optionTime) + { + return new Date( (int)optionInterpolator_.value( optionTime ) ); + } private void checkOptionDates() { if(!(optionDates_[0]>referenceDate())) diff --git a/QLNet/Termstructures/Yield/ZeroCurve.cs b/QLNet/Termstructures/Yield/ZeroCurve.cs index 12a06410c..f8cd65703 100644 --- a/QLNet/Termstructures/Yield/ZeroCurve.cs +++ b/QLNet/Termstructures/Yield/ZeroCurve.cs @@ -140,23 +140,29 @@ public InterpolatedZeroCurve(List dates, DayCounter dayCounter, Interpolator interpolator, Compounding compounding = Compounding.Continuous, - Frequency frequency = Frequency.Annual) - : base(dates[0], null, dayCounter) + Frequency frequency = Frequency.Annual, + Date refDate = null) + : base(refDate ?? dates[0], null, dayCounter) { times_ = new List(); dates_ = dates; data_ = yields; interpolator_ = interpolator; - initialize(compounding, frequency); + initialize( compounding, frequency, refDate ); } - private void initialize(Compounding compounding, Frequency frequency) + private void initialize( Compounding compounding, Frequency frequency, Date refDate = null ) { Utils.QL_REQUIRE(dates_.Count >= interpolator_.requiredPoints, () => "not enough input dates given"); Utils.QL_REQUIRE(data_.Count == dates_.Count, () => "dates/yields count mismatch"); times_ = new List(dates_.Count); - times_.Add(0.0); + double offset = 0.0; + if ( refDate != null ) + { + offset = dayCounter().yearFraction(refDate, dates_[0]); + } + times_.Add( offset ); if (compounding != Compounding.Continuous) { @@ -174,8 +180,9 @@ private void initialize(Compounding compounding, Frequency frequency) for (int i = 1; i < dates_.Count; i++) { Utils.QL_REQUIRE(dates_[i] > dates_[i - 1], () => "invalid date (" + dates_[i] + ", vs " + dates_[i - 1] + ")"); - times_.Add(dayCounter().yearFraction(dates_[0], dates_[i])); - Utils.QL_REQUIRE(!Utils.close(times_[i], times_[i - 1]), () => + times_.Add( dayCounter().yearFraction( refDate ?? dates_[0], dates_[i] ) ); + + Utils.QL_REQUIRE( !Utils.close( times_[i], times_[i - 1] ), () => "two dates correspond to the same time " + "under this curve's day count convention"); diff --git a/QLNet/Termstructures/YieldTermStructure.cs b/QLNet/Termstructures/YieldTermStructure.cs index e02d825b2..1a740e352 100644 --- a/QLNet/Termstructures/YieldTermStructure.cs +++ b/QLNet/Termstructures/YieldTermStructure.cs @@ -129,9 +129,12 @@ public double discount(double t, bool extrapolate = false) if (jumpTimes_[i]>0 && jumpTimes_[i] "invalid " + ( i + 1 ) + " jump quote" ); - double thisJump = jumps_[i].link.value(); - Utils.QL_REQUIRE( thisJump > 0.0 && thisJump <= 1.0, () => "invalid " + ( i + 1 ) + " jump value: " + thisJump ); - jumpEffect *= thisJump; + double thisJump = jumps_[i].link.value(); + Utils.QL_REQUIRE(thisJump > 0.0,()=> "invalid " + (i+1) + " jump value: " + thisJump); + #if !QL_NEGATIVE_RATES + Utils.QL_REQUIRE(thisJump <= 1.0,()=> "invalid " + (i+1) + " jump value: " + thisJump); + #endif + jumpEffect *= thisJump; } } return jumpEffect * discountImpl(t); diff --git a/QLNet/Time/Calendars/UnitedStates.cs b/QLNet/Time/Calendars/UnitedStates.cs index edef41470..3f5f0fd8f 100644 --- a/QLNet/Time/Calendars/UnitedStates.cs +++ b/QLNet/Time/Calendars/UnitedStates.cs @@ -106,229 +106,288 @@ third Monday in February against a list of known holidays. */ - public class UnitedStates : Calendar { - //! US calendars - public enum Market { - Settlement, //!< generic settlement calendar - NYSE, //!< New York stock exchange calendar - GovernmentBond, //!< government-bond calendar - NERC //!< off-peak days for NERC - }; + public class UnitedStates : Calendar + { + // a few rules used by multiple calendars + protected static bool isWashingtonBirthday( int d, Month m, int y, DayOfWeek w ) + { + if ( y >= 1971 ) + { + // third Monday in February + return ( d >= 15 && d <= 21 ) && w == DayOfWeek.Monday && m == Month.February; + } + else + { + // February 22nd, possily adjusted + return ( d == 22 || ( d == 23 && w == DayOfWeek.Monday ) + || ( d == 21 && w == DayOfWeek.Friday ) ) && m == Month.February; + } + } + + protected static bool isMemorialDay( int d, Month m, int y, DayOfWeek w ) + { + if ( y >= 1971 ) + { + // last Monday in May + return d >= 25 && w == DayOfWeek.Monday && m == Month.May; + } + else + { + // May 30th, possibly adjusted + return ( d == 30 || ( d == 31 && w == DayOfWeek.Monday ) + || ( d == 29 && w == DayOfWeek.Friday ) ) && m == Month.May; + } + } + + protected static bool isLaborDay( int d, Month m, int y, DayOfWeek w ) + { + // first Monday in September + return d <= 7 && w == DayOfWeek.Monday && m == Month.September; + } + + protected static bool isColumbusDay( int d, Month m, int y, DayOfWeek w ) + { + // second Monday in October + return ( d >= 8 && d <= 14 ) && w == DayOfWeek.Monday && m == Month.October + && y >= 1971; + } + + protected static bool isVeteransDay( int d, Month m, int y, DayOfWeek w ) + { + if ( y <= 1970 || y >= 1978 ) + { + // November 11th, adjusted + return ( d == 11 || ( d == 12 && w == DayOfWeek.Monday ) || + ( d == 10 && w == DayOfWeek.Friday ) ) && m == Month.November; + } + else + { + // fourth Monday in October + return ( d >= 22 && d <= 28 ) && w == DayOfWeek.Monday && m == Month.October; + } + } + + //! US calendars + public enum Market { + Settlement, //!< generic settlement calendar + NYSE, //!< New York stock exchange calendar + GovernmentBond, //!< government-bond calendar + NERC //!< off-peak days for NERC + }; - public UnitedStates() : this(Market.Settlement) { } - public UnitedStates(Market m) : base() { - switch (m) { - case Market.Settlement: - calendar_ = Settlement.Singleton; - break; - case Market.NYSE: - calendar_ = NYSE.Singleton; - break; - case Market.GovernmentBond: - calendar_ = GovernmentBond.Singleton; - break; - case Market.NERC: - calendar_ = NERC.Singleton; - break; - default: - throw new ArgumentException("Unknown market: " + m); - } - } + public UnitedStates() : this(Market.Settlement) { } + public UnitedStates(Market m) : base() { + switch (m) { + case Market.Settlement: + calendar_ = Settlement.Singleton; + break; + case Market.NYSE: + calendar_ = NYSE.Singleton; + break; + case Market.GovernmentBond: + calendar_ = GovernmentBond.Singleton; + break; + case Market.NERC: + calendar_ = NERC.Singleton; + break; + default: + throw new ArgumentException("Unknown market: " + m); + } + } - private class Settlement : Calendar.WesternImpl { - public static readonly Settlement Singleton = new Settlement(); - private Settlement() { } + private class Settlement : Calendar.WesternImpl { + public static readonly Settlement Singleton = new Settlement(); + private Settlement() { } - public override string name() { return "US settlement"; } - public override bool isBusinessDay(Date date) { - DayOfWeek w = date.DayOfWeek; - int d = date.Day; - Month m = (Month)date.Month; - int y = date.Year; - if (isWeekend(w) - // New Year's Day (possibly moved to Monday if on Sunday) - || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) - // (or to Friday if on Saturday) - || (d == 31 && w == DayOfWeek.Friday && m == Month.December) - // Martin Luther King's birthday (third Monday in January) - || ((d >= 15 && d <= 21) && w == DayOfWeek.Monday && m == Month.January && y >= 1983) - // Washington's birthday (third Monday in February) - || ((d >= 15 && d <= 21) && w == DayOfWeek.Monday && m == Month.February) - // Memorial Day (last Monday in May) - || (d >= 25 && w == DayOfWeek.Monday && m == Month.May) - // Independence Day (Monday if Sunday or Friday if Saturday) - || ((d == 4 || (d == 5 && w == DayOfWeek.Monday) || - (d == 3 && w == DayOfWeek.Friday)) && m == Month.July) - // Labor Day (first Monday in September) - || (d <= 7 && w == DayOfWeek.Monday && m == Month.September) - // Columbus Day (second Monday in October) - || ((d >= 8 && d <= 14) && w == DayOfWeek.Monday && m == Month.October) - // Veteran's Day (Monday if Sunday or Friday if Saturday) - || ((d == 11 || (d == 12 && w == DayOfWeek.Monday) || - (d == 10 && w == DayOfWeek.Friday)) && m == Month.November) - // Thanksgiving Day (fourth Thursday in November) - || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) - // Christmas (Monday if Sunday or Friday if Saturday) - || ((d == 25 || (d == 26 && w == DayOfWeek.Monday) || - (d == 24 && w == DayOfWeek.Friday)) && m == Month.December)) - return false; - return true; - } - } - private class NYSE : Calendar.WesternImpl { - public static readonly NYSE Singleton = new NYSE(); - private NYSE() { } + public override string name() { return "US settlement"; } + public override bool isBusinessDay(Date date) { + DayOfWeek w = date.DayOfWeek; + int d = date.Day; + Month m = (Month)date.Month; + int y = date.Year; + if (isWeekend(w) + // New Year's Day (possibly moved to Monday if on Sunday) + || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) + // (or to Friday if on Saturday) + || (d == 31 && w == DayOfWeek.Friday && m == Month.December) + // Martin Luther King's birthday (third Monday in January) + || ((d >= 15 && d <= 21) && w == DayOfWeek.Monday && m == Month.January && y >= 1983) + // Washington's birthday (third Monday in February) + || isWashingtonBirthday( d, m, y, w ) + // Memorial Day (last Monday in May) + || isMemorialDay( d, m, y, w ) + // Independence Day (Monday if Sunday or Friday if Saturday) + || ((d == 4 || (d == 5 && w == DayOfWeek.Monday) || + (d == 3 && w == DayOfWeek.Friday)) && m == Month.July) + // Labor Day (first Monday in September) + || isLaborDay( d, m, y, w ) + // Columbus Day (second Monday in October) + || isColumbusDay( d, m, y, w ) + // Veteran's Day (Monday if Sunday or Friday if Saturday) + || isVeteransDay( d, m, y, w ) + // Thanksgiving Day (fourth Thursday in November) + || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) + // Christmas (Monday if Sunday or Friday if Saturday) + || ((d == 25 || (d == 26 && w == DayOfWeek.Monday) || + (d == 24 && w == DayOfWeek.Friday)) && m == Month.December)) + return false; + return true; + } + } + private class NYSE : Calendar.WesternImpl { + public static readonly NYSE Singleton = new NYSE(); + private NYSE() { } - public override string name() { return "New York stock exchange"; } - public override bool isBusinessDay(Date date) { - DayOfWeek w = date.DayOfWeek; - int d = date.Day, dd = date.DayOfYear; - Month m = (Month)date.Month; - int y = date.Year; - int em = easterMonday(y); - if (isWeekend(w) - // New Year's Day (possibly moved to Monday if on Sunday) - || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) - // Washington's birthday (third Monday in February) - || ((d >= 15 && d <= 21) && w == DayOfWeek.Monday && m == Month.February) - // Good Friday - || (dd == em - 3) - // Memorial Day (last Monday in May) - || (d >= 25 && w == DayOfWeek.Monday && m == Month.May) - // Independence Day (Monday if Sunday or Friday if Saturday) - || ((d == 4 || (d == 5 && w == DayOfWeek.Monday) || - (d == 3 && w == DayOfWeek.Friday)) && m == Month.July) - // Labor Day (first Monday in September) - || (d <= 7 && w == DayOfWeek.Monday && m == Month.September) - // Thanksgiving Day (fourth Thursday in November) - || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) - // Christmas (Monday if Sunday or Friday if Saturday) - || ((d == 25 || (d == 26 && w == DayOfWeek.Monday) || - (d == 24 && w == DayOfWeek.Friday)) && m == Month.December) - ) return false; + public override string name() { return "New York stock exchange"; } + public override bool isBusinessDay(Date date) { + DayOfWeek w = date.DayOfWeek; + int d = date.Day, dd = date.DayOfYear; + Month m = (Month)date.Month; + int y = date.Year; + int em = easterMonday(y); + if (isWeekend(w) + // New Year's Day (possibly moved to Monday if on Sunday) + || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) + // Washington's birthday (third Monday in February) + || isWashingtonBirthday( d, m, y, w ) + // Good Friday + || (dd == em - 3) + // Memorial Day (last Monday in May) + || isMemorialDay( d, m, y, w ) + // Independence Day (Monday if Sunday or Friday if Saturday) + || ((d == 4 || (d == 5 && w == DayOfWeek.Monday) || + (d == 3 && w == DayOfWeek.Friday)) && m == Month.July) + // Labor Day (first Monday in September) + || isLaborDay( d, m, y, w ) + // Thanksgiving Day (fourth Thursday in November) + || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) + // Christmas (Monday if Sunday or Friday if Saturday) + || ((d == 25 || (d == 26 && w == DayOfWeek.Monday) || + (d == 24 && w == DayOfWeek.Friday)) && m == Month.December) + ) return false; - if ( y >= 1998 && ( d >= 15 && d <= 21 ) && w == DayOfWeek.Monday && m == Month.January ) - // Martin Luther King's birthday (third Monday in January) - return false; + if ( y >= 1998 && ( d >= 15 && d <= 21 ) && w == DayOfWeek.Monday && m == Month.January ) + // Martin Luther King's birthday (third Monday in January) + return false; - if ( ( y <= 1968 || ( y <= 1980 && y % 4 == 0 ) ) && m == Month.November - && d <= 7 && w == DayOfWeek.Tuesday ) - // Presidential election days - return false; + if ( ( y <= 1968 || ( y <= 1980 && y % 4 == 0 ) ) && m == Month.November + && d <= 7 && w == DayOfWeek.Tuesday ) + // Presidential election days + return false; - // Special closings - if (// Hurricane Sandy - ( y == 2012 && m == Month.October && ( d == 29 || d == 30 ) ) - // President Ford's funeral - || ( y == 2007 && m == Month.January && d == 2 ) - // President Reagan's funeral - || ( y == 2004 && m == Month.June && d == 11 ) - // September 11-14, 2001 - || ( y == 2001 && m == Month.September && ( 11 <= d && d <= 14 ) ) - // President Nixon's funeral - || ( y == 1994 && m == Month.April && d == 27 ) - // Hurricane Gloria - || ( y == 1985 && m == Month.September && d == 27 ) - // 1977 Blackout - || ( y == 1977 && m == Month.July && d == 14 ) - // Funeral of former President Lyndon B. Johnson. - || ( y == 1973 && m == Month.January && d == 25 ) - // Funeral of former President Harry S. Truman - || ( y == 1972 && m == Month.December && d == 28 ) - // National Day of Participation for the lunar exploration. - || ( y == 1969 && m == Month.July && d == 21 ) - // Funeral of former President Eisenhower. - || ( y == 1969 && m == Month.March && d == 31 ) - // Closed all day - heavy snow. - || ( y == 1969 && m == Month.February && d == 10 ) - // Day after Independence Day. - || ( y == 1968 && m == Month.July && d == 5 ) - // June 12-Dec. 31, 1968 - // Four day week (closed on Wednesdays) - Paperwork Crisis - || ( y == 1968 && dd >= 163 && w == DayOfWeek.Wednesday ) - // Day of mourning for Martin Luther King Jr. - || ( y == 1968 && m == Month.April && d == 9 ) - // Funeral of President Kennedy - || ( y == 1963 && m == Month.November && d == 25 ) - // Day before Decoration Day - || ( y == 1961 && m == Month.May && d == 29 ) - // Day after Christmas - || ( y == 1958 && m == Month.December && d == 26 ) - // Christmas Eve - || ((y == 1954 || y == 1956 || y == 1965) - && m == Month.December && d == 24 ) - ) - return false; - return true; - } - } - private class GovernmentBond : Calendar.WesternImpl { - public static readonly GovernmentBond Singleton = new GovernmentBond(); - private GovernmentBond() { } + // Special closings + if (// Hurricane Sandy + ( y == 2012 && m == Month.October && ( d == 29 || d == 30 ) ) + // President Ford's funeral + || ( y == 2007 && m == Month.January && d == 2 ) + // President Reagan's funeral + || ( y == 2004 && m == Month.June && d == 11 ) + // September 11-14, 2001 + || ( y == 2001 && m == Month.September && ( 11 <= d && d <= 14 ) ) + // President Nixon's funeral + || ( y == 1994 && m == Month.April && d == 27 ) + // Hurricane Gloria + || ( y == 1985 && m == Month.September && d == 27 ) + // 1977 Blackout + || ( y == 1977 && m == Month.July && d == 14 ) + // Funeral of former President Lyndon B. Johnson. + || ( y == 1973 && m == Month.January && d == 25 ) + // Funeral of former President Harry S. Truman + || ( y == 1972 && m == Month.December && d == 28 ) + // National Day of Participation for the lunar exploration. + || ( y == 1969 && m == Month.July && d == 21 ) + // Funeral of former President Eisenhower. + || ( y == 1969 && m == Month.March && d == 31 ) + // Closed all day - heavy snow. + || ( y == 1969 && m == Month.February && d == 10 ) + // Day after Independence Day. + || ( y == 1968 && m == Month.July && d == 5 ) + // June 12-Dec. 31, 1968 + // Four day week (closed on Wednesdays) - Paperwork Crisis + || ( y == 1968 && dd >= 163 && w == DayOfWeek.Wednesday ) + // Day of mourning for Martin Luther King Jr. + || ( y == 1968 && m == Month.April && d == 9 ) + // Funeral of President Kennedy + || ( y == 1963 && m == Month.November && d == 25 ) + // Day before Decoration Day + || ( y == 1961 && m == Month.May && d == 29 ) + // Day after Christmas + || ( y == 1958 && m == Month.December && d == 26 ) + // Christmas Eve + || ((y == 1954 || y == 1956 || y == 1965) + && m == Month.December && d == 24 ) + ) + return false; + return true; + } + } + private class GovernmentBond : Calendar.WesternImpl { + public static readonly GovernmentBond Singleton = new GovernmentBond(); + private GovernmentBond() { } - public override string name() { return "US government bond market"; } - public override bool isBusinessDay(Date date) { - DayOfWeek w = date.DayOfWeek; - int d = date.Day, dd = date.DayOfYear; - Month m = (Month)date.Month; - int y = date.Year; - int em = easterMonday(y); - if (isWeekend(w) - // New Year's Day (possibly moved to Monday if on Sunday) - || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) - // Martin Luther King's birthday (third Monday in January) - || ( ( d >= 15 && d <= 21 ) && w == DayOfWeek.Monday && m == Month.January && y >= 1983 ) - // Washington's birthday (third Monday in February) - || ((d >= 15 && d <= 21) && w == DayOfWeek.Monday && m == Month.February) - // Good Friday - || (dd == em - 3) - // Memorial Day (last Monday in May) - || (d >= 25 && w == DayOfWeek.Monday && m == Month.May) - // Independence Day (Monday if Sunday or Friday if Saturday) - || ((d == 4 || (d == 5 && w == DayOfWeek.Monday) || - (d == 3 && w == DayOfWeek.Friday)) && m == Month.July) - // Labor Day (first Monday in September) - || (d <= 7 && w == DayOfWeek.Monday && m == Month.September) - // Columbus Day (second Monday in October) - || ((d >= 8 && d <= 14) && w == DayOfWeek.Monday && m == Month.October) - // Veteran's Day (Monday if Sunday or Friday if Saturday) - || ((d == 11 || (d == 12 && w == DayOfWeek.Monday) || - (d == 10 && w == DayOfWeek.Friday)) && m == Month.November) - // Thanksgiving Day (fourth Thursday in November) - || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) - // Christmas (Monday if Sunday or Friday if Saturday) - || ((d == 25 || (d == 26 && w == DayOfWeek.Monday) || - (d == 24 && w == DayOfWeek.Friday)) && m == Month.December)) - return false; - return true; - } - } - private class NERC : Calendar.WesternImpl { - public static readonly NERC Singleton = new NERC(); - private NERC() { } + public override string name() { return "US government bond market"; } + public override bool isBusinessDay(Date date) { + DayOfWeek w = date.DayOfWeek; + int d = date.Day, dd = date.DayOfYear; + Month m = (Month)date.Month; + int y = date.Year; + int em = easterMonday(y); + if (isWeekend(w) + // New Year's Day (possibly moved to Monday if on Sunday) + || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) + // Martin Luther King's birthday (third Monday in January) + || ( ( d >= 15 && d <= 21 ) && w == DayOfWeek.Monday && m == Month.January && y >= 1983 ) + // Washington's birthday (third Monday in February) + || isWashingtonBirthday( d, m, y, w ) + // Good Friday + || (dd == em - 3) + // Memorial Day (last Monday in May) + || isMemorialDay( d, m, y, w ) + // Independence Day (Monday if Sunday or Friday if Saturday) + || ((d == 4 || (d == 5 && w == DayOfWeek.Monday) || + (d == 3 && w == DayOfWeek.Friday)) && m == Month.July) + // Labor Day (first Monday in September) + || isLaborDay( d, m, y, w ) + // Columbus Day (second Monday in October) + || isColumbusDay( d, m, y, w ) + // Veteran's Day (Monday if Sunday or Friday if Saturday) + || isVeteransDay( d, m, y, w ) + // Thanksgiving Day (fourth Thursday in November) + || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) + // Christmas (Monday if Sunday or Friday if Saturday) + || ((d == 25 || (d == 26 && w == DayOfWeek.Monday) || + (d == 24 && w == DayOfWeek.Friday)) && m == Month.December)) + return false; + return true; + } + } + private class NERC : Calendar.WesternImpl { + public static readonly NERC Singleton = new NERC(); + private NERC() { } - public override string name() { return "North American Energy Reliability Council"; } - public override bool isBusinessDay(Date date) { - DayOfWeek w = date.DayOfWeek; - int d = date.Day; - Month m = (Month)date.Month; - if (isWeekend(w) - // New Year's Day (possibly moved to Monday if on Sunday) - || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) - // Memorial Day (last Monday in May) - || (d >= 25 && w == DayOfWeek.Monday && m == Month.May) - // Independence Day (Monday if Sunday) - || ((d == 4 || (d == 5 && w == DayOfWeek.Monday)) && m == Month.July) - // Labor Day (first Monday in September) - || (d <= 7 && w == DayOfWeek.Monday && m == Month.September) - // Thanksgiving Day (fourth Thursday in November) - || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) - // Christmas (Monday if Sunday) - || ((d == 25 || (d == 26 && w == DayOfWeek.Monday)) && m == Month.December)) - return false; - return true; - } - } + public override string name() { return "North American Energy Reliability Council"; } + public override bool isBusinessDay(Date date) { + DayOfWeek w = date.DayOfWeek; + int d = date.Day; + Month m = (Month)date.Month; + int y = date.Year; + if (isWeekend(w) + // New Year's Day (possibly moved to Monday if on Sunday) + || ((d == 1 || (d == 2 && w == DayOfWeek.Monday)) && m == Month.January) + // Memorial Day (last Monday in May) + || isMemorialDay( d, m, y, w ) + // Independence Day (Monday if Sunday) + || ((d == 4 || (d == 5 && w == DayOfWeek.Monday)) && m == Month.July) + // Labor Day (first Monday in September) + || isLaborDay( d, m, y, w ) + // Thanksgiving Day (fourth Thursday in November) + || ((d >= 22 && d <= 28) && w == DayOfWeek.Thursday && m == Month.November) + // Christmas (Monday if Sunday) + || ((d == 25 || (d == 26 && w == DayOfWeek.Monday)) && m == Month.December)) + return false; + return true; + } + } } } diff --git a/QLNet/Time/Calendars/china.cs b/QLNet/Time/Calendars/china.cs index be133cdff..0d5ef56e0 100644 --- a/QLNet/Time/Calendars/china.cs +++ b/QLNet/Time/Calendars/china.cs @@ -122,6 +122,7 @@ public override bool isBusinessDay( Date date ) || ( y == 2014 && d >= 31 && m == Month.January ) || ( y == 2014 && d <= 6 && m == Month.February ) || ( y == 2015 && d >= 18 && d <= 24 && m == Month.February ) + || ( y == 2016 && d >= 8 && d <= 12 && m == Month.February ) // Ching Ming Festival || ( y <= 2008 && d == 4 && m == Month.April ) || ( y == 2009 && d == 6 && m == Month.April ) @@ -131,6 +132,7 @@ public override bool isBusinessDay( Date date ) || ( y == 2013 && d >= 4 && d <= 5 && m == Month.April ) || ( y == 2014 && d == 7 && m == Month.April ) || ( y == 2015 && d >= 5 && d <= 6 && m == Month.April ) + || ( y == 2016 && d == 4 && m == Month.April ) // Labor Day || ( y <= 2007 && d >= 1 && d <= 7 && m == Month.May ) || ( y == 2008 && d >= 1 && d <= 2 && m == Month.May ) @@ -143,6 +145,7 @@ public override bool isBusinessDay( Date date ) ( d == 1 && m == Month.May ) ) ) || ( y == 2014 && d >= 1 && d <= 3 && m == Month.May ) || ( y == 2015 && d == 1 && m == Month.May ) + || ( y == 2016 && d >= 1 && d <= 2 && m == Month.May ) // Tuen Ng Festival || ( y <= 2008 && d == 9 && m == Month.June ) || ( y == 2009 && ( d == 28 || d == 29 ) && m == Month.May ) @@ -152,6 +155,7 @@ public override bool isBusinessDay( Date date ) || ( y == 2013 && d >= 10 && d <= 12 && m == Month.June ) || ( y == 2014 && d == 2 && m == Month.June ) || ( y == 2015 && d == 22 && m == Month.June ) + || ( y == 2016 && d >= 9 && d <= 10 && m == Month.June ) // Mid-Autumn Festival || ( y <= 2008 && d == 15 && m == Month.September ) || ( y == 2010 && d >= 22 && d <= 24 && m == Month.September ) @@ -160,6 +164,7 @@ public override bool isBusinessDay( Date date ) || ( y == 2013 && d >= 19 && d <= 20 && m == Month.September ) || ( y == 2014 && d == 8 && m == Month.September ) || ( y == 2015 && d == 27 && m == Month.September ) + || ( y == 2016 && d >= 15 && d <= 16 && m == Month.September ) // National Day || ( y <= 2007 && d >= 1 && d <= 7 && m == Month.October ) || ( y == 2008 && ( ( d >= 29 && m == Month.September ) || @@ -171,6 +176,7 @@ public override bool isBusinessDay( Date date ) || ( y == 2013 && d >= 1 && d <= 7 && m == Month.October ) || ( y == 2014 && d >= 1 && d <= 7 && m == Month.October ) || ( y == 2015 && d >= 1 && d <= 7 && m == Month.October ) + || ( y == 2016 && d >= 3 && d <= 7 && m == Month.October ) // 70th anniversary of the victory of anti-Japaneses war || ( y == 2015 && d >= 3 && d <= 4 && m == Month.September ) ) @@ -279,7 +285,14 @@ public override bool isBusinessDay( Date date ) new Date(15,Month.February,2015), new Date(28,Month.February,2015), new Date(6,Month.September,2015), - new Date(10,Month.October,2015) + new Date(10,Month.October,2015), + // 2016 + new Date(6,Month.February,2016), + new Date(14,Month.February,2016), + new Date(12,Month.June,2016), + new Date(18,Month.September,2016), + new Date(8,Month.October,2016), + new Date(9,Month.October,2016) }; // If it is already a SSE business day, it must be a IB business day diff --git a/QLNet/Time/Calendars/japan.cs b/QLNet/Time/Calendars/japan.cs index 0c0cad34f..ca0d4e195 100644 --- a/QLNet/Time/Calendars/japan.cs +++ b/QLNet/Time/Calendars/japan.cs @@ -113,7 +113,7 @@ public override bool isBusinessDay(Date date) { || ((d == 20 || (d == 21 && w == DayOfWeek.Monday)) && m == Month.July && y >= 1996 && y < 2003) // Mountain Day (from 2016) - || ( d == 11 && m == Month.August && y >= 2016 ) + || ( ( d == 11 || ( d == 12 && w == DayOfWeek.Monday ) ) && m == Month.August && y >= 2016 ) // Respect for the Aged Day (3rd Monday in September), // was September 15th until 2003 || (w == DayOfWeek.Monday && (d >= 15 && d <= 21) && m == Month.September diff --git a/QLNet/Time/ECB.cs b/QLNet/Time/ECB.cs index 31df15987..009ba21ad 100644 --- a/QLNet/Time/ECB.cs +++ b/QLNet/Time/ECB.cs @@ -47,7 +47,7 @@ public static List knownDates() , 42032, 42074, 42116, 42165, 42207, 42256, 42305, 42347// 2015 // https://www.ecb.europa.eu/press/pr/date/2015/html/pr150622.en.html , 42396, 42445, 42487, 42529, 42578, 42627, 42669, 42718 // 2016 - , 42760 // 2017 + , 42760 , /*source ICAP */ 42802, 42844, 42893, 42942 // 2017 }; if (knownDateSet.empty()) diff --git a/QLNet/Time/Period.cs b/QLNet/Time/Period.cs index 1ec5a5b6d..7f37fc615 100644 --- a/QLNet/Time/Period.cs +++ b/QLNet/Time/Period.cs @@ -1,6 +1,6 @@ /* Copyright (C) 2008 Siarhei Novik (snovik@gmail.com) - Copyright (C) 2008-2013 Andrea Maggiulli (a.maggiulli@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -20,7 +20,7 @@ under the terms of the QLNet license. You should have received a using System; namespace QLNet { - public class Period { + public class Period : IComparable { private int length_; private TimeUnit unit_; @@ -351,5 +351,14 @@ public string ToShortString() { throw new Exception("unknown time unit (" + units() + ")"); } } + + public int CompareTo( object obj ) + { + if ( this < (Period)obj ) + return -1; + else if ( this == (Period)obj ) + return 0; + else return 1; + } } } diff --git a/QLNet/processes/BlackScholesProcess.cs b/QLNet/processes/BlackScholesProcess.cs index 6bfd3cb42..a931c8be6 100644 --- a/QLNet/processes/BlackScholesProcess.cs +++ b/QLNet/processes/BlackScholesProcess.cs @@ -1,5 +1,6 @@ /* Copyright (C) 2008, 2009 Siarhei Novik (snovik@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -18,217 +19,312 @@ under the terms of the QLNet license. You should have received a */ using System; -namespace QLNet { - //! Generalized Black-Scholes stochastic process - /*! This class describes the stochastic process governed by - \f[ - dS(t, S) = (r(t) - q(t) - \frac{\sigma(t, S)^2}{2}) dt - + \sigma dW_t. - \f] - - \ingroup processes - */ - public class GeneralizedBlackScholesProcess : StochasticProcess1D { - private Handle x0_; - public Handle stateVariable() { return x0_; } - - private Handle riskFreeRate_, dividendYield_; - public Handle riskFreeRate() { return riskFreeRate_; } - public Handle dividendYield() { return dividendYield_; } - - private Handle blackVolatility_; - public Handle blackVolatility() { return blackVolatility_; } - - private RelinkableHandle localVolatility_ = new RelinkableHandle(); - public Handle localVolatility() { - if (!updated_) { - - // constant Black vol? - BlackConstantVol constVol = blackVolatility().link as BlackConstantVol; - if (constVol != null) { - // ok, the local vol is constant too. - localVolatility_.linkTo(new LocalConstantVol(constVol.referenceDate(), - constVol.blackVol(0.0, x0_.link.value()), - constVol.dayCounter())); - updated_ = true; - return localVolatility_; - } - - // ok, so it's not constant. Maybe it's strike-independent? - BlackVarianceCurve volCurve = blackVolatility().link as BlackVarianceCurve; - if (volCurve != null) { - // ok, we can use the optimized algorithm - localVolatility_.linkTo(new LocalVolCurve(new Handle(volCurve))); - updated_ = true; - return localVolatility_; - } - - // ok, so it's strike-dependent. Never mind. - localVolatility_.linkTo(new LocalVolSurface(blackVolatility_, riskFreeRate_, dividendYield_, x0_.link.value())); - updated_ = true; - return localVolatility_; - - } else { - return localVolatility_; +namespace QLNet +{ + //! Generalized Black-Scholes stochastic process + /*! This class describes the stochastic process \f$ S \f$ governed by + \f[ + d\ln S(t) = (r(t) - q(t) - \frac{\sigma(t, S)^2}{2}) dt + + \sigma dW_t. + \f] + + \warning while the interface is expressed in terms of \f$ S \f$, + the internal calculations work on \f$ ln S \f$. + + \ingroup processes + */ + public class GeneralizedBlackScholesProcess : StochasticProcess1D + { + + public GeneralizedBlackScholesProcess( Handle x0, Handle dividendTS, + Handle riskFreeTS, Handle blackVolTS, IDiscretization1D disc = null ) + : base( disc ?? new EulerDiscretization() ) + { + x0_ = x0; + riskFreeRate_ = riskFreeTS; + dividendYield_ = dividendTS; + blackVolatility_ = blackVolTS; + updated_ = false; + + x0_.registerWith( update ); + riskFreeRate_.registerWith( update ); + dividendYield_.registerWith( update ); + blackVolatility_.registerWith( update ); + } + + public override double x0() + { + return x0_.link.value(); + } + + /*! \todo revise extrapolation */ + public override double drift( double t, double x ) + { + double sigma = diffusion( t, x ); + // we could be more anticipatory if we know the right dt for which the drift will be used + double t1 = t + 0.0001; + return riskFreeRate_.link.forwardRate( t, t1, Compounding.Continuous, Frequency.NoFrequency, true ).rate() + - dividendYield_.link.forwardRate( t, t1, Compounding.Continuous, Frequency.NoFrequency, true ).rate() + - 0.5 * sigma * sigma; + } + + /*! \todo revise extrapolation */ + public override double diffusion( double t, double x ) + { + return localVolatility().link.localVol( t, x, true ); + } + + public override double apply( double x0, double dx ) + { + return x0 * Math.Exp( dx ); + } + + /*! \warning raises a "not implemented" exception. It should + be rewritten to return the expectation E(S) of + the process, not exp(E(log S)). + */ + public override double expectation( double t0, double x0, double dt ) + { + localVolatility(); // trigger update + if ( isStrikeIndependent_ ) + { + // exact value for curves + return x0 * + Math.Exp( dt * ( riskFreeRate_.link.forwardRate( t0, t0 + dt, Compounding.Continuous, + Frequency.NoFrequency, true ).value() - + dividendYield_.link.forwardRate( + t0, t0 + dt, Compounding.Continuous, Frequency.NoFrequency, true ).value() ) ); + } + else + { + Utils.QL_FAIL( "not implemented" ); + return 0; + } + } + public override double stdDeviation(double t0, double x0, double dt) + { + localVolatility(); // trigger update + if(isStrikeIndependent_) + { + // exact value for curves + return Math.Sqrt(variance(t0,x0,dt)); + } + else + { + return discretization_.diffusion(this,t0,x0,dt); + } + } + public override double variance(double t0, double x0, double dt) + { + localVolatility(); // trigger update + if ( isStrikeIndependent_ ) + { + // exact value for curves + return blackVolatility_.link.blackVariance( t0 + dt, 0.01 ) - + blackVolatility_.link.blackVariance( t0, 0.01 ); + } + else + { + return discretization_.variance( this, t0, x0, dt ); + } + } + public override double evolve(double t0, double x0, double dt, double dw) + { + localVolatility(); // trigger update + if (isStrikeIndependent_) + { + // exact value for curves + double var = variance(t0, x0, dt); + double drift = (riskFreeRate_.link.forwardRate(t0, t0 + dt, Compounding.Continuous, + Frequency.NoFrequency, true).value() - + dividendYield_.link.forwardRate(t0, t0 + dt, Compounding.Continuous, + Frequency.NoFrequency, true).value()) * + dt - 0.5 * var; + return apply(x0, Math.Sqrt(var) * dw + drift); + } + else + return apply(x0, discretization_.drift(this, t0, x0, dt) + stdDeviation(t0, x0, dt) * dw); + } + public override double time( Date d ) + { + return riskFreeRate_.link.dayCounter().yearFraction( riskFreeRate_.link.referenceDate(), d ); + } + public override void update() + { + updated_ = false; + base.update(); + } + public Handle stateVariable() + { + return x0_; + } + public Handle dividendYield() + { + return dividendYield_; + } + public Handle riskFreeRate() + { + return riskFreeRate_; + } + public Handle blackVolatility() + { + return blackVolatility_; + } + public Handle localVolatility() + { + if ( !updated_ ) + { + isStrikeIndependent_ = true; + + // constant Black vol? + BlackConstantVol constVol = blackVolatility().link as BlackConstantVol; + if ( constVol != null ) + { + // ok, the local vol is constant too. + localVolatility_.linkTo( new LocalConstantVol( constVol.referenceDate(), + constVol.blackVol( 0.0, x0_.link.value() ), + constVol.dayCounter() ) ); + updated_ = true; + return localVolatility_; } - } - - private bool updated_; - - - public GeneralizedBlackScholesProcess(Handle x0, Handle dividendTS, - Handle riskFreeTS, Handle blackVolTS) - : this(x0, dividendTS, riskFreeTS, blackVolTS, new EulerDiscretization()) { } - public GeneralizedBlackScholesProcess(Handle x0, Handle dividendTS, - Handle riskFreeTS, Handle blackVolTS, - IDiscretization1D disc) - : base(disc) { - x0_ = x0; - riskFreeRate_ = riskFreeTS; - dividendYield_ = dividendTS; - blackVolatility_ = blackVolTS; - updated_ = false; - - x0_.registerWith(update); - riskFreeRate_.registerWith(update); - dividendYield_.registerWith(update); - blackVolatility_.registerWith(update); - } - - public override double x0() { return x0_.link.value(); } - - /*! \todo revise extrapolation */ - public override double drift(double t, double x) { - double sigma = diffusion(t, x); - // we could be more anticipatory if we know the right dt for which the drift will be used - double t1 = t + 0.0001; - return riskFreeRate_.link.forwardRate(t,t1, Compounding.Continuous,Frequency.NoFrequency,true).rate() - - dividendYield_.link.forwardRate(t, t1, Compounding.Continuous, Frequency.NoFrequency, true).rate() - - 0.5 * sigma * sigma; - } - - /*! \todo revise extrapolation */ - public override double diffusion(double t, double x) { - return localVolatility().link.localVol(t, x, true); - } - - public override double apply(double x0, double dx) { - return x0 * Math.Exp(dx); - } - - /*! \warning raises a "not implemented" exception. It should - be rewritten to return the expectation E(S) of - the process, not exp(E(log S)). - */ - public override double expectation(double t0, double x0, double dt) { - throw new NotImplementedException(); - } - - public override double evolve(double t0, double x0, double dt, double dw) { - return apply(x0, discretization_.drift(this, t0, x0, dt) + stdDeviation(t0, x0, dt) * dw); - } - - public override double time(Date d) { - return riskFreeRate_.link.dayCounter().yearFraction(riskFreeRate_.link.referenceDate(), d); - } - - public override void update() { - updated_ = false; - base.update(); - } - } - - - //! Black-Scholes (1973) stochastic process - /*! This class describes the stochastic process for a stock given by - \f[ - dS(t, S) = (r(t) - \frac{\sigma(t, S)^2}{2}) dt + \sigma dW_t. - \f] - - \ingroup processes - */ - public class BlackScholesProcess : GeneralizedBlackScholesProcess { - public BlackScholesProcess(Handle x0, - Handle riskFreeTS, - Handle blackVolTS) - : this(x0, riskFreeTS, blackVolTS, new EulerDiscretization()) {} - public BlackScholesProcess(Handle x0, - Handle riskFreeTS, - Handle blackVolTS, - IDiscretization1D d) - : base(x0, - // no dividend yield - new Handle(new FlatForward(0, new NullCalendar(), 0.0, new Actual365Fixed())), - riskFreeTS, blackVolTS, d) { } - } - - - //! Merton (1973) extension to the Black-Scholes stochastic process - /*! This class describes the stochastic process for a stock or - stock index paying a continuous dividend yield given by - \f[ - dS(t, S) = (r(t) - q(t) - \frac{\sigma(t, S)^2}{2}) dt - + \sigma dW_t. - \f] - - \ingroup processes - */ - public class BlackScholesMertonProcess : GeneralizedBlackScholesProcess { - public BlackScholesMertonProcess(Handle x0, - Handle dividendTS, - Handle riskFreeTS, - Handle blackVolTS) - : this(x0, dividendTS, riskFreeTS, blackVolTS, new EulerDiscretization()) { } - public BlackScholesMertonProcess(Handle x0, - Handle dividendTS, - Handle riskFreeTS, - Handle blackVolTS, - IDiscretization1D d) - : base(x0, dividendTS, riskFreeTS, blackVolTS, d) { } - } - - - //! Black (1976) stochastic process - /*! This class describes the stochastic process for a forward or - futures contract given by - \f[ - dS(t, S) = \frac{\sigma(t, S)^2}{2} dt + \sigma dW_t. - \f] - - \ingroup processes - */ - public class BlackProcess : GeneralizedBlackScholesProcess { - public BlackProcess(Handle x0, - Handle riskFreeTS, - Handle blackVolTS) - : this(x0, riskFreeTS, blackVolTS, new EulerDiscretization()) { } - public BlackProcess(Handle x0, - Handle riskFreeTS, - Handle blackVolTS, - IDiscretization1D d) - : base(x0, riskFreeTS, riskFreeTS, blackVolTS, d) { } - } - - - //! Garman-Kohlhagen (1983) stochastic process - /*! This class describes the stochastic process for an exchange - rate given by - \f[ - dS(t, S) = (r(t) - r_f(t) - \frac{\sigma(t, S)^2}{2}) dt - + \sigma dW_t. - \f] - - \ingroup processes - */ - public class GarmanKohlagenProcess : GeneralizedBlackScholesProcess { - public GarmanKohlagenProcess(Handle x0, - Handle foreignRiskFreeTS, - Handle domesticRiskFreeTS, - Handle blackVolTS) - : this(x0, foreignRiskFreeTS, domesticRiskFreeTS, blackVolTS, new EulerDiscretization()) { } - public GarmanKohlagenProcess(Handle x0, Handle foreignRiskFreeTS, - Handle domesticRiskFreeTS, - Handle blackVolTS, IDiscretization1D d) - : base( x0, foreignRiskFreeTS, domesticRiskFreeTS, blackVolTS, d ) { } - } + + // ok, so it's not constant. Maybe it's strike-independent? + BlackVarianceCurve volCurve = blackVolatility().link as BlackVarianceCurve; + if ( volCurve != null ) + { + // ok, we can use the optimized algorithm + localVolatility_.linkTo( new LocalVolCurve( new Handle( volCurve ) ) ); + updated_ = true; + return localVolatility_; + } + + // ok, so it's strike-dependent. Never mind. + localVolatility_.linkTo( new LocalVolSurface( blackVolatility_, riskFreeRate_, dividendYield_, + x0_.link.value() ) ); + updated_ = true; + isStrikeIndependent_ = false; + return localVolatility_; + } + else + { + return localVolatility_; + } + } + + private Handle x0_; + private Handle riskFreeRate_, dividendYield_; + private Handle blackVolatility_; + private RelinkableHandle localVolatility_ = new RelinkableHandle(); + private bool updated_, isStrikeIndependent_; + } + + //! Black-Scholes (1973) stochastic process + /*! This class describes the stochastic process S for a stock given by + \f[ + dS(t, S) = (r(t) - \frac{\sigma(t, S)^2}{2}) dt + \sigma dW_t. + \f] + + \ingroup processes + */ + + public class BlackScholesProcess : GeneralizedBlackScholesProcess + { + public BlackScholesProcess(Handle x0, + Handle riskFreeTS, + Handle blackVolTS) + : this(x0, riskFreeTS, blackVolTS, new EulerDiscretization()) + {} + + public BlackScholesProcess(Handle x0, + Handle riskFreeTS, + Handle blackVolTS, + IDiscretization1D d) + : base(x0, + // no dividend yield + new Handle(new FlatForward(0, new NullCalendar(), 0.0, new Actual365Fixed())), + riskFreeTS, blackVolTS, d) + {} + } + + //! Merton (1973) extension to the Black-Scholes stochastic process + /*! This class describes the stochastic process for a stock or + stock index paying a continuous dividend yield given by + \f[ + dS(t, S) = (r(t) - q(t) - \frac{\sigma(t, S)^2}{2}) dt + + \sigma dW_t. + \f] + + \ingroup processes + */ + + public class BlackScholesMertonProcess : GeneralizedBlackScholesProcess + { + public BlackScholesMertonProcess(Handle x0, + Handle dividendTS, + Handle riskFreeTS, + Handle blackVolTS) + : this(x0, dividendTS, riskFreeTS, blackVolTS, new EulerDiscretization()) + {} + + public BlackScholesMertonProcess(Handle x0, + Handle dividendTS, + Handle riskFreeTS, + Handle blackVolTS, + IDiscretization1D d) + : base(x0, dividendTS, riskFreeTS, blackVolTS, d) + {} + } + + //! Black (1976) stochastic process + /*! This class describes the stochastic process for a forward or + futures contract given by + \f[ + dS(t, S) = \frac{\sigma(t, S)^2}{2} dt + \sigma dW_t. + \f] + + \ingroup processes + */ + + public class BlackProcess : GeneralizedBlackScholesProcess + { + public BlackProcess(Handle x0, + Handle riskFreeTS, + Handle blackVolTS) + : this(x0, riskFreeTS, blackVolTS, new EulerDiscretization()) + {} + + public BlackProcess(Handle x0, + Handle riskFreeTS, + Handle blackVolTS, + IDiscretization1D d) + : base(x0, riskFreeTS, riskFreeTS, blackVolTS, d) + {} + } + + //! Garman-Kohlhagen (1983) stochastic process + /*! This class describes the stochastic process for an exchange + rate given by + \f[ + dS(t, S) = (r(t) - r_f(t) - \frac{\sigma(t, S)^2}{2}) dt + + \sigma dW_t. + \f] + + \ingroup processes + */ + + public class GarmanKohlagenProcess : GeneralizedBlackScholesProcess + { + public GarmanKohlagenProcess(Handle x0, + Handle foreignRiskFreeTS, + Handle domesticRiskFreeTS, + Handle blackVolTS) + : this(x0, foreignRiskFreeTS, domesticRiskFreeTS, blackVolTS, new EulerDiscretization()) + {} + + public GarmanKohlagenProcess(Handle x0, Handle foreignRiskFreeTS, + Handle domesticRiskFreeTS, + Handle blackVolTS, IDiscretization1D d) + : base(x0, foreignRiskFreeTS, domesticRiskFreeTS, blackVolTS, d) + {} + } } diff --git a/README.md b/README.md index f887bb98b..c83ff2542 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ QLNet also contains new developments on the bond market like MBS, Amortized Cost [![NuGet](https://buildstats.info/nuget/qlnet)](https://www.nuget.org/packages/qlnet/) [![Stars](https://img.shields.io/github/stars/amaggiulli/qlnet.svg)](https://github.com/amaggiulli/qlnet/stargazers) [![Coverity](https://scan.coverity.com/projects/7000/badge.svg)](https://scan.coverity.com/projects/amaggiulli-qlnet) +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?item_name=Donation+to+QLNet&cmd=_donations&business=a.maggiulli%40gmail.com) ## Development workflow diff --git a/Test/Properties/AssemblyInfo.cs b/Test/Properties/AssemblyInfo.cs index fab0525d9..ca1991865 100644 --- a/Test/Properties/AssemblyInfo.cs +++ b/Test/Properties/AssemblyInfo.cs @@ -14,7 +14,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Test")] -[assembly: AssemblyCopyright( "Copyright (c) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com)" )] +[assembly: AssemblyCopyright( "Copyright (c) 2008-2017 Andrea Maggiulli (a.maggiulli@gmail.com)" )] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -35,5 +35,5 @@ // // È possibile specificare tutti i valori oppure impostare i valori predefiniti per i numeri relativi alla build e alla revisione // utilizzando l'asterisco (*) come descritto di seguito: -[assembly: AssemblyVersion( "1.8.0.0" )] -[assembly: AssemblyFileVersion( "1.8.0.0" )] +[assembly: AssemblyVersion( "1.9.0.0" )] +[assembly: AssemblyFileVersion( "1.9.0.0" )] diff --git a/Test/T_AsianOptions.cs b/Test/T_AsianOptions.cs index 2113d8651..16ea681e4 100644 --- a/Test/T_AsianOptions.cs +++ b/Test/T_AsianOptions.cs @@ -697,6 +697,74 @@ public void testAnalyticDiscreteGeometricAveragePriceGreeks() } } + // Issue #115 + public void testIssue115() + { + DateTime timer = DateTime.Now; + + // set up dates + Calendar calendar = new TARGET(); + Date todaysDate = new Date( 1, Month.January, 2017 ); + Date settlementDate = new Date( 1, Month.January, 2017 ); + Date maturity = new Date( 17, Month.May, 2018 ); + Settings.setEvaluationDate( todaysDate ); + + // our options + Option.Type type = Option.Type.Call; + double underlying = 100; + double strike = 100; + double dividendYield = 0.00; + double riskFreeRate = 0.06; + double volatility = 0.20; + + DayCounter dayCounter = new Actual365Fixed(); + Exercise europeanExercise = new EuropeanExercise( maturity ); + + double? accumulator = underlying; + int? pastfixingcount = 1; + List fixings = new List(); + fixings.Add( new Date( 1, 1, 2018 ) ); + + Handle underlyingH = new Handle( new SimpleQuote( underlying ) ); + // bootstrap the yield/dividend/vol curves + var flatTermStructure = new Handle( new FlatForward( settlementDate, riskFreeRate, dayCounter ) ); + var flatDividendTS = new Handle( new FlatForward( settlementDate, dividendYield, dayCounter ) ); + var flatVolTS = new Handle( new BlackConstantVol( settlementDate, calendar, volatility, dayCounter ) ); + StrikedTypePayoff payoff = new PlainVanillaPayoff( type, strike ); + var bsmProcess = new BlackScholesMertonProcess( underlyingH, flatDividendTS, flatTermStructure, flatVolTS ); + + // options + VanillaOption europeanOption = new VanillaOption( payoff, europeanExercise ); + PlainVanillaPayoff callpayoff = new PlainVanillaPayoff( type, strike ); + + DiscreteAveragingAsianOption asianoption = new DiscreteAveragingAsianOption( + Average.Type.Arithmetic, + accumulator, + pastfixingcount, + fixings, + callpayoff, + europeanExercise ); + + int minSamples = 10000; + int maxSamples = 10000; + ulong seed = 42; + double tolerance = 1.0; + + var pricingengine = new MCDiscreteArithmeticAPEngine( + bsmProcess, + 252, + false, + false, + false, + minSamples, + tolerance, + maxSamples, + seed ); + + asianoption.setPricingEngine( pricingengine ); + + double price = asianoption.NPV(); + } // public struct DiscreteAverageData // { diff --git a/Test/T_BarrierOption.cs b/Test/T_BarrierOption.cs new file mode 100644 index 000000000..be78749ea --- /dev/null +++ b/Test/T_BarrierOption.cs @@ -0,0 +1,1016 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ + #if QL_DOTNET_FRAMEWORK + [TestClass()] + #endif + public class T_BarrierOption + { + private void REPORT_FAILURE( string greekName, + Barrier.Type barrierType, + double barrier, + double rebate, + StrikedTypePayoff payoff, + Exercise exercise, + double s, + double q, + double r, + Date today, + double v, + double expected, + double calculated, + double error, + double tolerance ) + { + QAssert.Fail( barrierType + " " + + exercise + " " + + payoff.optionType() + " option with " + + payoff + " payoff:\n" + + " underlying value: " + s + "\n" + + " strike: " + payoff.strike() + "\n" + + " barrier: " + barrier + "\n" + + " rebate: " + rebate + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + + private void REPORT_FX_FAILURE( string greekName, + Barrier.Type barrierType, + double barrier, + double rebate, + StrikedTypePayoff payoff, + Exercise exercise, + double s, + double q, + double r, + Date today, + double vol25Put, + double atmVol, + double vol25Call, + double v, + double expected, + double calculated, + double error, + double tolerance ) + { + QAssert.Fail( barrierType + " " + + exercise + " " + + payoff.optionType() + " option with " + + payoff + " payoff:\n" + + " underlying value: " + s + "\n" + + " strike: " + payoff.strike() + "\n" + + " barrier: " + barrier + "\n" + + " rebate: " + rebate + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " 25PutVol: " + vol25Put + "\n" + + " atmVol: " + atmVol + "\n" + + " 25CallVol: " + vol25Call + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + + private struct BarrierOptionData + { + public BarrierOptionData(Barrier.Type type_,double volatility_,double strike_,double barrier_,double callValue_, + double putValue_) + { + type=type_; + volatility = volatility_; + strike = strike_; + barrier = barrier_; + callValue = callValue_; + putValue = putValue_; + } + + public Barrier.Type type; + public double volatility; + public double strike; + public double barrier; + public double callValue; + public double putValue; + } + + private struct NewBarrierOptionData + { + public NewBarrierOptionData( Barrier.Type barrierType_,double barrier_,double rebate_,Option.Type type_, + Exercise.Type exType_,double strike_,double s_,double q_,double r_,double t_,double v_,double result_, + double tol_) + { + barrierType = barrierType_; + barrier = barrier_; + rebate = rebate_; + type = type_; + exType = exType_; + strike = strike_; + s = s_; + q = q_; + r = r_; + t = t_; + v = v_; + result = result_; + tol = tol_; + } + + public Barrier.Type barrierType; + public double barrier; + public double rebate; + public Option.Type type; + public Exercise.Type exType; + public double strike; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double t; // time to maturity + public double v; // volatility + public double result; // result + public double tol; // tolerance + } + + private struct BarrierFxOptionData + { + public Barrier.Type barrierType; + public double barrier; + public double rebate; + public Option.Type type; + public double strike; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double t; // time to maturity + public double vol25Put; // 25 delta put vol + public double volAtm; // atm vol + public double vol25Call; // 25 delta call vol + public double v; // volatility at strike + public double result; // result + public double tol; // tolerance + + public BarrierFxOptionData(Barrier.Type barrierType, double barrier, double rebate, Option.Type type, + double strike, double s, double q, double r, double t, double vol25Put, double volAtm, double vol25Call, + double v, double result, double tol) : this() + { + this.barrierType = barrierType; + this.barrier = barrier; + this.rebate = rebate; + this.type = type; + this.strike = strike; + this.s = s; + this.q = q; + this.r = r; + this.t = t; + this.vol25Put = vol25Put; + this.volAtm = volAtm; + this.vol25Call = vol25Call; + this.v = v; + this.result = result; + this.tol = tol; + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testHaugValues() + { + // Testing barrier options against Haug's values + Exercise.Type european = Exercise.Type.European; + Exercise.Type american = Exercise.Type.American; + NewBarrierOptionData[] values = { + /* The data below are from + "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 pag. 72 + */ + // barrierType, barrier, rebate, type, exercise, strk, s, q, r, t, v, result, tol + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 9.0246, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 6.7924, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 4.8759, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.6789, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 2.3580, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.3453, 1.0e-4), + + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 7.7627, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 4.0109, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.0576, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 13.8333, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 7.8494, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.9795, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 14.1112, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 8.4482, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 4.5910, 1.0e-4), + + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 8.8334, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 7.0285, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 5.4137, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 2.6341, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4389, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4315, 1.0e-4), + + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 9.0093, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 5.1370, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 2.8517, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 14.8816, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 9.2045, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 5.3043, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 15.2098, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 9.7278, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 5.8350, 1.0e-4), + + + // barrierType, barrier, rebate, type, exercise, strk, s, q, r, t, v, result, tol + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.2798, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 2.2947, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.6252, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.7760, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 5.4932, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 7.5187, 1.0e-4 ), + + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.9586, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 6.5677, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 11.9752, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.2845, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 5.9085, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 11.6465, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 1.4653, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.3721, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 7.0846, 1.0e-4 ), + + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4170, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4258, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 2.6246, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 4.2293, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 5.8032, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 7.5649, 1.0e-4 ), + + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.8769, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 7.7989, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 13.3078, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.3328, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 7.2636, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 12.9713, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Put, european, 90, 100.0, 0.04, 0.08, 0.50, 0.30, 2.0658, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Put, european, 100, 100.0, 0.04, 0.08, 0.50, 0.30, 4.4226, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Put, european, 110, 100.0, 0.04, 0.08, 0.50, 0.30, 8.3686, 1.0e-4 ), + + // Options with american exercise: values computed with 400 steps of Haug's VBA code (handles only out options) + // barrierType, barrier, rebate, type, exercise, strk, s, q, r, t, v, result, tol + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 0.0, Option.Type.Call, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 10.4655, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 0.0, Option.Type.Call, american, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 4.5159, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 0.0, Option.Type.Call, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.5971, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, american, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Call, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 0.0, Option.Type.Call, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 11.8076, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 0.0, Option.Type.Call, american, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.3993, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 3.0, Option.Type.Call, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.3457, 1.0e-4), + + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 3.0, Option.Type.Put, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.2795, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 0.0, Option.Type.Put, american, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.3512, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 95.0, 0.0, Option.Type.Put, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 11.5773, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, american, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.DownOut, 100.0, 3.0, Option.Type.Put, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 0.0, Option.Type.Put, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 1.4763, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 0.0, Option.Type.Put, american, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.3001, 1.0e-4 ), + new NewBarrierOptionData( Barrier.Type.UpOut, 105.0, 0.0, Option.Type.Put, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 10.0000, 1.0e-4 ), + + // some american in-options - results (roughly) verified with other numerical methods + // barrierType, barrier, rebate, type, exercise, strk, s, q, r, t, v, result, tol + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 7.7615, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, american, 100, 100.0, 0.04, 0.08, 0.50, 0.25, 4.0118, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 95.0, 3.0, Option.Type.Call, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.0544, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.DownIn, 100.0, 3.0, Option.Type.Call, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 13.8308, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, american, 90, 100.0, 0.04, 0.08, 0.50, 0.25, 14.1150, 1.0e-4), + new NewBarrierOptionData( Barrier.Type.UpIn, 105.0, 3.0, Option.Type.Call, american, 110, 100.0, 0.04, 0.08, 0.50, 0.25, 4.5900, 1.0e-4), + + /* + Data from "Going to Extreme: Correcting Simulation Bias in Exotic + Option Valuation" + D.R. Beaglehole, P.H. Dybvig and G. Zhou + Financial Analysts Journal; Jan / Feb 1997; 53, 1 + */ + // barrierType, barrier, rebate, type, strike, s, q, r, t, v, result, tol + // { Barrier::DownOut, 45.0, 0.0, Option::Put, 50, 50.0,-0.05, 0.10, 0.25, 0.50, 4.032, 1.0e-3 }, + // { Barrier::DownOut, 45.0, 0.0, Option::Put, 50, 50.0,-0.05, 0.10, 1.00, 0.50, 5.477, 1.0e-3 } + + }; + + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i< values.Length; i++) + { + Date exDate = today + Convert.ToInt32(values[i].t*360+0.5); + + spot .setValue(values[i].s); + qRate.setValue(values[i].q); + rRate.setValue(values[i].r); + vol .setValue(values[i].v); + + StrikedTypePayoff payoff = new PlainVanillaPayoff(values[i].type,values[i].strike); + + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess( new Handle(spot), + new Handle(qTS),new Handle(rTS), + new Handle(volTS)); + + Exercise exercise; + if (values[i].exType == Exercise.Type.European) + exercise = new EuropeanExercise(exDate); + else + exercise = new AmericanExercise(exDate); + + BarrierOption barrierOption = new BarrierOption(values[i].barrierType,values[i].barrier,values[i].rebate, + payoff,exercise); + + IPricingEngine engine; + double calculated; + double expected; + double error; + if (values[i].exType == Exercise.Type.European) + { + // these engines support only european options + engine = new AnalyticBarrierEngine(stochProcess); + + barrierOption.setPricingEngine(engine); + + calculated = barrierOption.NPV(); + expected = values[i].result; + error = Math.Abs(calculated-expected); + if (error>values[i].tol) { + REPORT_FAILURE("value", values[i].barrierType, values[i].barrier, + values[i].rebate, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].v, + expected, calculated, error, values[i].tol); + } + + // TODO FdBlackScholesBarrierEngine + //engine = new FdBlackScholesBarrierEngine(stochProcess, 200, 400); + //barrierOption.setPricingEngine(engine); + + //calculated = barrierOption.NPV(); + //expected = values[i].result; + //error = Math.Abs(calculated-expected); + //if (error>5.0e-3) { + // REPORT_FAILURE("fd value", values[i].barrierType, values[i].barrier, + // values[i].rebate, payoff, exercise, values[i].s, + // values[i].q, values[i].r, today, values[i].v, + // expected, calculated, error, values[i].tol); + //} + } + + engine = new BinomialBarrierEngine( + (d, end, steps, strike) => new CoxRossRubinstein(d, end, steps, strike), + (args, process, grid) => new DiscretizedBarrierOption(args, process, grid), + stochProcess, 400 ); + barrierOption.setPricingEngine(engine); + + calculated = barrierOption.NPV(); + expected = values[i].result; + error = Math.Abs(calculated-expected); + double tol = 1.1e-2; + if (error>tol) { + REPORT_FAILURE("Binomial (Boyle-lau) value", values[i].barrierType, values[i].barrier, + values[i].rebate, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].v, + expected, calculated, error, tol); + } + + // Note: here, to test Derman convergence, we force maxTimeSteps to + // timeSteps, effectively disabling Boyle-Lau barrier adjustment. + // Production code should always enable Boyle-Lau. In most cases it + // gives very good convergence with only a modest timeStep increment. + + + engine = new BinomialBarrierEngine( + ( d, end, steps, strike ) => new CoxRossRubinstein( d, end, steps, strike ), + ( args, process, grid ) => new DiscretizedDermanKaniBarrierOption( args, process, grid ), + stochProcess, 400 ); + barrierOption.setPricingEngine( engine ); + calculated = barrierOption.NPV(); + expected = values[i].result; + error = Math.Abs( calculated - expected ); + tol = 4e-2; + if ( error > tol ) + { + REPORT_FAILURE( "Binomial (Derman) value", values[i].barrierType, values[i].barrier, + values[i].rebate, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].v, + expected, calculated, error, tol ); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testBabsiriValues() + { + // Testing barrier options against Babsiri's values + + /* + Data from + "Simulating Path-Dependent Options: A New Approach" + - M. El Babsiri and G. Noel + Journal of Derivatives; Winter 1998; 6, 2 + */ + BarrierOptionData[] values = { + new BarrierOptionData( Barrier.Type.DownIn, 0.10, 100, 90, 0.07187, 0.0 ), + new BarrierOptionData( Barrier.Type.DownIn, 0.15, 100, 90, 0.60638, 0.0 ), + new BarrierOptionData( Barrier.Type.DownIn, 0.20, 100, 90, 1.64005, 0.0 ), + new BarrierOptionData( Barrier.Type.DownIn, 0.25, 100, 90, 2.98495, 0.0 ), + new BarrierOptionData( Barrier.Type.DownIn, 0.30, 100, 90, 4.50952, 0.0 ), + new BarrierOptionData( Barrier.Type.UpIn, 0.10, 100, 110, 4.79148, 0.0 ), + new BarrierOptionData( Barrier.Type.UpIn, 0.15, 100, 110, 7.08268, 0.0 ), + new BarrierOptionData( Barrier.Type.UpIn, 0.20, 100, 110, 9.11008, 0.0 ), + new BarrierOptionData( Barrier.Type.UpIn, 0.25, 100, 110, 11.06148, 0.0 ), + new BarrierOptionData( Barrier.Type.UpIn, 0.30, 100, 110, 12.98351, 0.0 ) + }; + + double underlyingPrice = 100.0; + double rebate = 0.0; + double r = 0.05; + double q = 0.02; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + SimpleQuote underlying = new SimpleQuote(underlyingPrice); + + SimpleQuote qH_SME = new SimpleQuote(q); + YieldTermStructure qTS = Utilities.flatRate(today, qH_SME, dc); + + SimpleQuote rH_SME = new SimpleQuote(r); + YieldTermStructure rTS = Utilities.flatRate(today, rH_SME, dc); + + SimpleQuote volatility = new SimpleQuote(0.10); + BlackVolTermStructure volTS = Utilities.flatVol(today, volatility, dc); + + Date exDate = today+360; + Exercise exercise = new EuropeanExercise(exDate); + + for (int i=0; i< values.Length; i++) + { + volatility.setValue(values[i].volatility); + + StrikedTypePayoff callPayoff = new PlainVanillaPayoff(Option.Type.Call,values[i].strike); + + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess( + new Handle(underlying), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + + IPricingEngine engine = new AnalyticBarrierEngine(stochProcess); + + // analytic + BarrierOption barrierCallOption = new BarrierOption(values[i].type,values[i].barrier,rebate,callPayoff,exercise); + barrierCallOption.setPricingEngine(engine); + double calculated = barrierCallOption.NPV(); + double expected = values[i].callValue; + double error = Math.Abs(calculated-expected); + double maxErrorAllowed = 1.0e-5; + if (error>maxErrorAllowed) + { + REPORT_FAILURE("value", values[i].type, values[i].barrier, + rebate, callPayoff, exercise, underlyingPrice, + q, r, today, values[i].volatility, + expected, calculated, error, maxErrorAllowed); + } + + // TODO MakeMCBarrierEngine + //double maxMcRelativeErrorAllowed = 2.0e-2; + + //IPricingEngine mcEngine = + // MakeMCBarrierEngine(stochProcess) + // .withStepsPerYear(1) + // .withBrownianBridge() + // .withSamples(131071) // 2^17-1 + // .withMaxSamples(1048575) // 2^20-1 + // .withSeed(5); + + //barrierCallOption.setPricingEngine(mcEngine); + //calculated = barrierCallOption.NPV(); + //error = std::fabs(calculated-expected)/expected; + //if (error>maxMcRelativeErrorAllowed) { + // REPORT_FAILURE("value", values[i].type, values[i].barrier, + // rebate, callPayoff, exercise, underlyingPrice, + // q, r, today, values[i].volatility, + // expected, calculated, error, + // maxMcRelativeErrorAllowed); + //} + + } + + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testBeagleholeValues() + { + // Testing barrier options against Beaglehole's values + + + /* + Data from + "Going to Extreme: Correcting Simulation Bias in Exotic + Option Valuation" + - D.R. Beaglehole, P.H. Dybvig and G. Zhou + Financial Analysts Journal; Jan / Feb 1997; 53, 1 + */ + BarrierOptionData[] values = { + new BarrierOptionData( Barrier.Type.DownOut, 0.50, 50, 45, 5.477, 0.0) + }; + + double underlyingPrice = 50.0; + double rebate = 0.0; + double r = Math.Log(1.1); + double q = 0.00; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote underlying = new SimpleQuote(underlyingPrice); + + SimpleQuote qH_SME = new SimpleQuote(q); + YieldTermStructure qTS = Utilities.flatRate(today, qH_SME, dc); + + SimpleQuote rH_SME = new SimpleQuote(r); + YieldTermStructure rTS = Utilities.flatRate(today, rH_SME, dc); + + SimpleQuote volatility = new SimpleQuote(0.10); + BlackVolTermStructure volTS = Utilities.flatVol(today, volatility, dc); + + + Date exDate = today+360; + Exercise exercise = new EuropeanExercise(exDate); + + for (int i=0; i(underlying), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new AnalyticBarrierEngine(stochProcess); + + BarrierOption barrierCallOption = new BarrierOption(values[i].type,values[i].barrier,rebate,callPayoff,exercise); + barrierCallOption.setPricingEngine(engine); + double calculated = barrierCallOption.NPV(); + double expected = values[i].callValue; + double maxErrorAllowed = 1.0e-3; + double error = Math.Abs(calculated-expected); + if (error > maxErrorAllowed) + { + REPORT_FAILURE("value", values[i].type, values[i].barrier, + rebate, callPayoff, exercise, underlyingPrice, + q, r, today, values[i].volatility, + expected, calculated, error, maxErrorAllowed); + } + + // TODO MakeMCBarrierEngine + //double maxMcRelativeErrorAllowed = 0.01; + //IPricingEngine mcEngine = + // MakeMCBarrierEngine(stochProcess) + // .withStepsPerYear(1) + // .withBrownianBridge() + // .withSamples(131071) // 2^17-1 + // .withMaxSamples(1048575) // 2^20-1 + // .withSeed(10); + + //barrierCallOption.setPricingEngine(mcEngine); + //calculated = barrierCallOption.NPV(); + //error = Math.Abs(calculated-expected)/expected; + //if (error>maxMcRelativeErrorAllowed) + //{ + // REPORT_FAILURE("value", values[i].type, values[i].barrier, + // rebate, callPayoff, exercise, underlyingPrice, + // q, r, today, values[i].volatility, + // expected, calculated, error, + // maxMcRelativeErrorAllowed); + //} + } + } + + public void testLocalVolAndHestonComparison() + { + // Testing local volatility and Heston FD engines for barrier options + SavedSettings backup = new SavedSettings(); + + Date settlementDate = new Date(5, Month.July, 2002); + Settings.setEvaluationDate(settlementDate); + + DayCounter dayCounter = new Actual365Fixed(); + Calendar calendar = new TARGET(); + + int[] t = { 13, 41, 75, 165, 256, 345, 524, 703 }; + double[] r = { 0.0357,0.0349,0.0341,0.0355,0.0359,0.0368,0.0386,0.0401 }; + + List rates = new InitializedList(1, 0.0357); + List dates = new InitializedList(1, settlementDate); + for (int i = 0; i < 8; ++i) + { + dates.Add(settlementDate + t[i]); + rates.Add(r[i]); + } + + Handle rTS = new Handle( new InterpolatedZeroCurve(dates, rates, dayCounter)); + Handle qTS = new Handle(Utilities.flatRate(settlementDate, 0.0, dayCounter)); + + Handle s0 = new Handle(new SimpleQuote(4500.00)); + + double[] tmp = { 100 ,500 ,2000,3400,3600,3800,4000,4200,4400,4500, + 4600,4800,5000,5200,5400,5600,7500,10000,20000,30000 }; + List strikes = new List(tmp); + + double[] v = + { 1.015873, 1.015873, 1.015873, 0.89729, 0.796493, 0.730914, 0.631335, 0.568895, + 0.711309, 0.711309, 0.711309, 0.641309, 0.635593, 0.583653, 0.508045, 0.463182, + 0.516034, 0.500534, 0.500534, 0.500534, 0.448706, 0.416661, 0.375470, 0.353442, + 0.516034, 0.482263, 0.447713, 0.387703, 0.355064, 0.337438, 0.316966, 0.306859, + 0.497587, 0.464373, 0.430764, 0.374052, 0.344336, 0.328607, 0.310619, 0.301865, + 0.479511, 0.446815, 0.414194, 0.361010, 0.334204, 0.320301, 0.304664, 0.297180, + 0.461866, 0.429645, 0.398092, 0.348638, 0.324680, 0.312512, 0.299082, 0.292785, + 0.444801, 0.413014, 0.382634, 0.337026, 0.315788, 0.305239, 0.293855, 0.288660, + 0.428604, 0.397219, 0.368109, 0.326282, 0.307555, 0.298483, 0.288972, 0.284791, + 0.420971, 0.389782, 0.361317, 0.321274, 0.303697, 0.295302, 0.286655, 0.282948, + 0.413749, 0.382754, 0.354917, 0.316532, 0.300016, 0.292251, 0.284420, 0.281164, + 0.400889, 0.370272, 0.343525, 0.307904, 0.293204, 0.286549, 0.280189, 0.277767, + 0.390685, 0.360399, 0.334344, 0.300507, 0.287149, 0.281380, 0.276271, 0.274588, + 0.383477, 0.353434, 0.327580, 0.294408, 0.281867, 0.276746, 0.272655, 0.271617, + 0.379106, 0.349214, 0.323160, 0.289618, 0.277362, 0.272641, 0.269332, 0.268846, + 0.377073, 0.347258, 0.320776, 0.286077, 0.273617, 0.269057, 0.266293, 0.266265, + 0.399925, 0.369232, 0.338895, 0.289042, 0.265509, 0.255589, 0.249308, 0.249665, + 0.423432, 0.406891, 0.373720, 0.314667, 0.281009, 0.263281, 0.246451, 0.242166, + 0.453704, 0.453704, 0.453704, 0.381255, 0.334578, 0.305527, 0.268909, 0.251367, + 0.517748, 0.517748, 0.517748, 0.416577, 0.364770, 0.331595, 0.287423, 0.264285 }; + + Matrix blackVolMatrix = new Matrix(strikes.Count, dates.Count-1); + for (int i=0; i < strikes.Count; ++i) + for (int j=1; j < dates.Count; ++j) + { + blackVolMatrix[i,j-1] = v[i*(dates.Count-1)+j-1]; + } + + BlackVarianceSurface volTS = new BlackVarianceSurface(settlementDate, calendar, + dates,strikes, blackVolMatrix,dayCounter); + volTS.setInterpolation(); + GeneralizedBlackScholesProcess localVolProcess = new BlackScholesMertonProcess(s0, qTS, rTS, + new Handle(volTS)); + + double v0 = 0.195662; + double kappa = 5.6628; + double theta = 0.0745911; + double sigma = 1.1619; + double rho = -0.511493; + + HestonProcess hestonProcess = new HestonProcess(rTS, qTS, s0, v0,kappa, theta, sigma, rho); + + HestonModel hestonModel = new HestonModel(hestonProcess); + + // TODO FdHestonBarrierEngine + //IPricingEngine fdHestonEngine = new FdHestonBarrierEngine(hestonModel, 100, 400, 50); + + // TODO FdBlackScholesBarrierEngine + //IPricingEngine fdLocalVolEngine = new FdBlackScholesBarrierEngine(localVolProcess,100, 400, 0,FdmSchemeDesc.Douglas(), true, 0.35); + + double strike = s0.link.value(); + double barrier = 3000; + double rebate = 100; + Date exDate = settlementDate + new Period(20, TimeUnit.Months); + + StrikedTypePayoff payoff = new PlainVanillaPayoff(Option.Type.Put, strike); + + Exercise exercise = new EuropeanExercise(exDate); + + BarrierOption barrierOption = new BarrierOption(Barrier.Type.DownOut,barrier, rebate, payoff, exercise); + + // TODO FdHestonBarrierEngine + //barrierOption.setPricingEngine(fdHestonEngine); + double expectedHestonNPV = 111.5; + double calculatedHestonNPV = barrierOption.NPV(); + + // TODO FdBlackScholesBarrierEngine + //barrierOption.setPricingEngine(fdLocalVolEngine); + double expectedLocalVolNPV = 132.8; + double calculatedLocalVolNPV = barrierOption.NPV(); + + double tol = 0.01; + + if (Math.Abs(expectedHestonNPV - calculatedHestonNPV) > tol*expectedHestonNPV) + { + Assert.Fail("Failed to reproduce Heston barrier price for " + + "\n strike: " + payoff.strike() + + "\n barrier: " + barrier + + "\n maturity: " + exDate + + "\n calculated: " + calculatedHestonNPV + + "\n expected: " + expectedHestonNPV); + } + if (Math.Abs(expectedLocalVolNPV - calculatedLocalVolNPV) > tol*expectedLocalVolNPV) + { + Assert.Fail("Failed to reproduce Heston barrier price for " + + "\n strike: " + payoff.strike() + + "\n barrier: " + barrier + + "\n maturity: " + exDate + + "\n calculated: " + calculatedLocalVolNPV + + "\n expected: " + expectedLocalVolNPV); + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testVannaVolgaSimpleBarrierValues() + { + // Testing barrier FX options against Vanna/Volga values + SavedSettings backup = new SavedSettings(); + + BarrierFxOptionData[] values = { + + //barrierType,barrier,rebate,type,strike,s,q,r,t,vol25Put,volAtm,vol25Call,vol, result, tol + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Call,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.148127, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Call,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.075943, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Call,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.0274771, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Call,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.00573, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Call,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.00012, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Put,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.00697606, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Put,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.020078, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Put,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.0489395, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Put,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.0969877, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.5,0, Option.Type.Put,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.157, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Call,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.0322202, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Call,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.0241491, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Call,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.0164275, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Call,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.01, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Call,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.00489, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Put,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.000560713, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Put,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.000546804, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Put,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.000130649, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Put,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.000300828, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.5,0, Option.Type.Put,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.00135, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Call,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.17746, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Call,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.0994142, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Call,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.0439, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Call,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.01574, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Call,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.00501, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.00612, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.00426, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.00257, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.00122, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.00045, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Put,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.00022, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Put,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.00284, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Put,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.02032, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Put,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.058235, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.1,0, Option.Type.Put,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.109432, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.00017, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.00083, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Call,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.00289, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Call,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.00067784, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Call,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Call,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Call,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.17423, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.09584, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.04133, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.01452, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.00456, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Put,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.00732, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Put,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.01778, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Put,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.02875, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Put,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.0390535, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.1,0, Option.Type.Put,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.0489236, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.13321,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.11638,0.00753, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.22687,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.10088,0.02062, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.31179,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08925,0.04907, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.38843,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08463,0.09711, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.46047,1.30265,0.0003541,0.0033871,1,0.10087,0.08925,0.08463,0.08412,0.15752, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Call,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.20493, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Call,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.105577, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Call,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.0358872, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Call,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.00634958, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Call,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Put,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.0108218, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Put,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.0313339, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Put,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.0751237, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Put,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.153407, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpOut,1.6,0, Option.Type.Put,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.253767, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Call,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.05402, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Call,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.0410069, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Call,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.0279562, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Call,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.0173055, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Call,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.00764, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Put,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.000962737, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Put,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.00102637, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Put,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.000419834, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Put,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.00159277, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.UpIn,1.6,0, Option.Type.Put,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.00473629, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Call,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.255098, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Call,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.145701, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Call,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.06384, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Call,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.02366, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Call,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.00764, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.00592, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.00421, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.00256, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.0012, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Call,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.0004, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Put,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Put,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.00280549, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Put,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.0279945, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Put,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.0896352, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1,0, Option.Type.Put,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.175182, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511, 0.00000, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089, 0.00000, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444, 0.00000, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.0002, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownOut,1.3,0, Option.Type.Put,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.00096, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Call,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.00384783, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Call,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.000883232, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Call,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Call,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197, 0.00000, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Call,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261, 0.00000, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.25302, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.14238, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.06128, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.02245, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Call,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.00725, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Put,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.01178, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Put,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.0295548, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Put,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.047549, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Put,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.0653642, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1,0, Option.Type.Put,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.0833221, 1.0e-4), + + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.06145,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.12511,0.01178, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.19545,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.1089,0.03236, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.32238,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09444,0.07554, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.44298,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09197,0.15479, 1.0e-4), + new BarrierFxOptionData( Barrier.Type.DownIn,1.3,0, Option.Type.Put,1.56345,1.30265,0.0009418,0.0039788,2,0.10891,0.09525,0.09197,0.09261,0.25754, 1.0e-4), + + }; + + DayCounter dc = new Actual365Fixed(); + Date today = new Date(5, Month.March, 2013); + Settings.setEvaluationDate(today); + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol25Put = new SimpleQuote(0.0); + SimpleQuote volAtm = new SimpleQuote(0.0); + SimpleQuote vol25Call = new SimpleQuote(0.0); + + for (int i=0; i< values.Length; i++) + { + spot.setValue(values[i].s); + qRate.setValue(values[i].q); + rRate.setValue(values[i].r); + vol25Put.setValue(values[i].vol25Put); + volAtm.setValue(values[i].volAtm); + vol25Call.setValue(values[i].vol25Call); + + StrikedTypePayoff payoff = new PlainVanillaPayoff(values[i].type,values[i].strike); + Date exDate = today + (int)(values[i].t*365+0.5); + Exercise exercise = new EuropeanExercise(exDate); + + Handle volAtmQuote = new Handle( + new DeltaVolQuote( new Handle(volAtm), + DeltaVolQuote.DeltaType.Fwd, + values[i].t, + DeltaVolQuote.AtmType.AtmDeltaNeutral)); + + Handle vol25PutQuote = new Handle( + new DeltaVolQuote( -0.25, + new Handle(vol25Put), + values[i].t, + DeltaVolQuote.DeltaType.Fwd)); + + Handle vol25CallQuote = new Handle( + new DeltaVolQuote( 0.25, + new Handle(vol25Call), + values[i].t, + DeltaVolQuote.DeltaType.Fwd)); + + BarrierOption barrierOption = new BarrierOption(values[i].barrierType,values[i].barrier,values[i].rebate, + payoff,exercise); + + double bsVanillaPrice = Utils.blackFormula(values[i].type, values[i].strike, + spot.value()*qTS.discount(values[i].t)/rTS.discount(values[i].t), + values[i].v * Math.Sqrt(values[i].t), rTS.discount(values[i].t)); + IPricingEngine vannaVolgaEngine = new VannaVolgaBarrierEngine( volAtmQuote, vol25PutQuote, vol25CallQuote, + new Handle (spot), + new Handle (rTS), + new Handle (qTS), + true, + bsVanillaPrice); + barrierOption.setPricingEngine(vannaVolgaEngine); + + double calculated = barrierOption.NPV(); + double expected = values[i].result; + double error = Math.Abs(calculated-expected); + if (error>values[i].tol) + { + REPORT_FX_FAILURE( "value", values[i].barrierType, values[i].barrier, + values[i].rebate, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].vol25Put, + values[i].volAtm, values[i].vol25Call, values[i].v, + expected, calculated, error, values[i].tol); + } + } + } + } +} diff --git a/Test/T_BinaryOption.cs b/Test/T_BinaryOption.cs new file mode 100644 index 000000000..6bd9bddf2 --- /dev/null +++ b/Test/T_BinaryOption.cs @@ -0,0 +1,283 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ + #if QL_DOTNET_FRAMEWORK + [TestClass()] + #endif + public class T_BinaryOption + { + private void REPORT_FAILURE( string greekName, + StrikedTypePayoff payoff, + Exercise exercise, + Barrier.Type barrierType, + double barrier, + double s, + double q, + double r, + Date today, + double v, + double expected, + double calculated, + double error, + double tolerance ) + { + QAssert.Fail( payoff.optionType() + " option with " + + barrierType + " barrier type:\n" + + " barrier: " + barrier + "\n" + + payoff + " payoff:\n" + + exercise + " " + + payoff.optionType() + + " spot value: " + s + "\n" + + " strike: " + payoff.strike() + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + + private struct BinaryOptionData + { + public Barrier.Type barrierType; + public double barrier; + public double cash; // cash payoff for cash-or-nothing + public Option.Type type; + public double strike; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double t; // time to maturity + public double v; // volatility + public double result; // expected result + public double tol; // tolerance + + public BinaryOptionData(Barrier.Type barrierType, double barrier, double cash, Option.Type type, double strike, + double s, double q, double r, double t, double v, double result, double tol) : this() + { + this.barrierType = barrierType; + this.barrier = barrier; + this.cash = cash; + this.type = type; + this.strike = strike; + this.s = s; + this.q = q; + this.r = r; + this.t = t; + this.v = v; + this.result = result; + this.tol = tol; + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testCashOrNothingHaugValues() + { + // Testing cash-or-nothing barrier options against Haug's values + + BinaryOptionData[] values = { + /* The data below are from + "Option pricing formulas 2nd Ed.", E.G. Haug, McGraw-Hill 2007 pag. 180 - cases 13,14,17,18,21,22,25,26 + Note: + q is the dividend rate, while the book gives b, the cost of carry (q=r-b) + */ + // barrierType, barrier, cash, type, strike, spot, q, r, t, vol, value, tol + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 15.00, Option.Type.Call, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.9289, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 15.00, Option.Type.Call, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 6.2150, 1e-4 ), + // following value is wrong in book. + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Call, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 5.8926, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Call, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 7.4519, 1e-4 ), + // 17,18 + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 15.00, Option.Type.Put, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.4314, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 15.00, Option.Type.Put, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 3.1454, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Put, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 5.3297, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Put, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 3.7704, 1e-4 ), + // 21,22 + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 15.00, Option.Type.Call, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.8758, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 15.00, Option.Type.Call, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.9081, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 15.00, Option.Type.Call, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 15.00, Option.Type.Call, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0407, 1e-4 ), + // 25,26 + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 15.00, Option.Type.Put, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0323, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 15.00, Option.Type.Put, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 15.00, Option.Type.Put, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 3.0461, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 15.00, Option.Type.Put, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 3.0054, 1e-4 ), + + // other values calculated with book vba + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Call, 102.00, 95.00,-0.14, 0.10, 0.5, 0.20, 8.6806, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Call, 102.00, 95.00, 0.03, 0.10, 0.5, 0.20, 5.3112, 1e-4 ), + // degenerate conditions (barrier touched) + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 15.00, Option.Type.Call, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 7.4926, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Call, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 11.1231, 1e-4 ), + // 17,18 + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 15.00, Option.Type.Put, 102.00, 98.00, 0.00, 0.10, 0.5, 0.20, 7.1344, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 15.00, Option.Type.Put, 102.00, 101.00, 0.00, 0.10, 0.5, 0.20, 5.9299, 1e-4 ), + // 21,22 + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 15.00, Option.Type.Call, 98.00, 99.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 15.00, Option.Type.Call, 98.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + // 25,26 + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 15.00, Option.Type.Put, 98.00, 99.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 15.00, Option.Type.Put, 98.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(100.0); + SimpleQuote qRate = new SimpleQuote(0.04); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.01); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.25); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new AnalyticBinaryBarrierEngine(stochProcess); + + BarrierOption opt = new BarrierOption(values[i].barrierType,values[i].barrier, 0,payoff,amExercise); + + opt.setPricingEngine(engine); + + double calculated = opt.NPV(); + double error = Math.Abs(calculated-values[i].result); + if (error > values[i].tol) { + REPORT_FAILURE("value", payoff, amExercise, values[i].barrierType, + values[i].barrier, values[i].s, + values[i].q, values[i].r, today, values[i].v, + values[i].result, calculated, error, values[i].tol); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testAssetOrNothingHaugValues() + { + // Testing asset-or-nothing barrier options against Haug's values + + BinaryOptionData[] values = { + /* The data below are from + "Option pricing formulas 2nd Ed.", E.G. Haug, McGraw-Hill 2007 pag. 180 - cases 15,16,19,20,23,24,27,28 + Note: + q is the dividend rate, while the book gives b, the cost of carry (q=r-b) + */ + // barrierType, barrier, cash, type, strike, spot, q, r, t, vol, value, tol + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 0.00, Option.Type.Call, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 37.2782, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 0.00, Option.Type.Call, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 45.8530, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 0.00, Option.Type.Call, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 44.5294, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 0.00, Option.Type.Call, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 54.9262, 1e-4 ), + // 19,20 + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 0.00, Option.Type.Put, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 27.5644, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownIn, 100.00, 0.00, Option.Type.Put, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 18.9896, 1e-4 ), + // following value is wrong in book. + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 0.00, Option.Type.Put, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 33.1723, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpIn, 100.00, 0.00, Option.Type.Put, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 22.7755, 1e-4 ), + // 23,24 + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 0.00, Option.Type.Call, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 39.9391, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 0.00, Option.Type.Call, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 40.1574, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 0.00, Option.Type.Call, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 0.00, Option.Type.Call, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.2676, 1e-4 ), + // 27,28 + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 0.00, Option.Type.Put, 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.2183, 1e-4 ), + new BinaryOptionData( Barrier.Type.DownOut, 100.00, 0.00, Option.Type.Put, 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0000, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 0.00, Option.Type.Put, 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 17.2983, 1e-4 ), + new BinaryOptionData( Barrier.Type.UpOut, 100.00, 0.00, Option.Type.Put, 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 17.0306, 1e-4 ), + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(100.0); + SimpleQuote qRate = new SimpleQuote(0.04); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.01); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.25); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new AnalyticBinaryBarrierEngine(stochProcess); + + BarrierOption opt = new BarrierOption(values[i].barrierType,values[i].barrier, 0,payoff,amExercise); + + opt.setPricingEngine(engine); + + double calculated = opt.NPV(); + double error = Math.Abs(calculated-values[i].result); + if (error > values[i].tol) + { + REPORT_FAILURE("value", payoff, amExercise, values[i].barrierType, + values[i].barrier, values[i].s, + values[i].q, values[i].r, today, values[i].v, + values[i].result, calculated, error, values[i].tol); + } + } + } + } +} diff --git a/Test/T_BlackDeltaCalculator.cs b/Test/T_BlackDeltaCalculator.cs new file mode 100644 index 000000000..3a03c34e5 --- /dev/null +++ b/Test/T_BlackDeltaCalculator.cs @@ -0,0 +1,701 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_BlackDeltaCalculator + { + private int timeToDays( double t ) + { + // FLOATING_POINT_EXCEPTION + return (int)( t * 360 + 0.5 ); + } + + private struct DeltaData + { + public Option.Type ot; + public DeltaVolQuote.DeltaType dt; + public double spot; + public double dDf; // domestic discount + public double fDf; // foreign discount + public double stdDev; + public double strike; + public double value; + + public DeltaData(Option.Type ot, DeltaVolQuote.DeltaType dt, double spot, double dDf, double fDf, + double stdDev, double strike, double value) : this() + { + this.ot = ot; + this.dt = dt; + this.spot = spot; + this.dDf = dDf; + this.fDf = fDf; + this.stdDev = stdDev; + this.strike = strike; + this.value = value; + } + } + + private struct EuropeanOptionData + { + public Option.Type type; + public double strike; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double t; // time to maturity + public double v; // volatility + public double result; // expected result + public double tol; // tolerance + + public EuropeanOptionData(Option.Type type, double strike, double s, double q, double r, double t, + double v, double result, double tol) : this() + { + this.type = type; + this.strike = strike; + this.s = s; + this.q = q; + this.r = r; + this.t = t; + this.v = v; + this.result = result; + this.tol = tol; + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testDeltaValues() + { + // Testing delta calculator values + + DeltaData[] values = { + // Values taken from parallel implementation in R + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Spot, 1.421, 0.997306, 0.992266, 0.1180654, 1.608080, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaSpot, 1.421, 0.997306, 0.992266, 0.1180654, 1.600545, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Fwd, 1.421, 0.997306, 0.992266, 0.1180654, 1.609029, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaFwd, 1.421, 0.997306, 0.992266, 0.1180654, 1.601550, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Spot, 122.121, 0.9695434,0.9872347, 0.0887676, 119.8031, 0.67), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaSpot, 122.121, 0.9695434,0.9872347, 0.0887676, 117.7096, 0.67), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Fwd, 122.121, 0.9695434,0.9872347, 0.0887676, 120.0592, 0.67), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaFwd, 122.121, 0.9695434,0.9872347, 0.0887676, 118.0532, 0.67), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.Spot, 3.4582, 0.99979, 0.9250616, 0.3199034, 4.964924, -0.821), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.PaSpot, 3.4582, 0.99979, 0.9250616, 0.3199034, 3.778327, -0.821), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.Fwd, 3.4582, 0.99979, 0.9250616, 0.3199034, 4.51896, -0.821), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.PaFwd, 3.4582, 0.99979, 0.9250616, 0.3199034, 3.65728, -0.8219), + // JPYUSD Data taken from Castagnas "FX Options and Smile Risk" (Wiley 2009) + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.Spot, 103.00, 0.99482, 0.98508, 0.07247845, 97.47, -0.25), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.PaSpot, 103.00, 0.99482, 0.98508, 0.07247845, 97.22, -0.25) + }; + + Option.Type currOt; + DeltaVolQuote.DeltaType currDt; + double currSpot; + double currdDf; + double currfDf; + double currStdDev; + double currStrike; + double expected; + double currDelta; + double calculated; + double error; + double tolerance; + + for (int i=0; itolerance) + { + Assert.Fail("\n Delta-from-strike calculation failed for delta. \n" + + "Iteration: "+ i + "\n" + + "Calculated Strike:" + calculated + "\n" + + "Expected Strike:" + expected + "\n" + + "Error: " + error); + } + + tolerance=1.0e-2; + // tolerance not that small, but sufficient for strikes in + // particular since they might be results of a numerical + // procedure + + expected =currStrike; + calculated =myCalc.strikeFromDelta(currDelta); + error =Math.Abs(calculated-expected); + + if (error>tolerance) + { + Assert.Fail("\n Strike-from-delta calculation failed for delta. \n" + + "Iteration: "+ i + "\n" + + "Calculated Strike:" + calculated + "\n" + + "Expected Strike:" + expected + "\n" + + "Error: " + error); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testDeltaPriceConsistency() + { + // Testing premium-adjusted delta price consistency + + // This function tests for price consistencies with the standard + // Black Scholes calculator, since premium adjusted deltas can be calculated + // from spot deltas by adding/subtracting the premium. + + SavedSettings backup = new SavedSettings(); + + // actually, value and tol won't be needed for testing + EuropeanOptionData[] values = { + // type, strike, spot, rd, rf, t, vol, value, tol + new EuropeanOptionData( Option.Type.Call, 0.9123, 1.2212, 0.0231, 0.0000, 0.25, 0.301, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 0.9234, 1.2212, 0.0231, 0.0000, 0.35, 0.111, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 0.9783, 1.2212, 0.0231, 0.0000, 0.45, 0.071, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 1.0000, 1.2212, 0.0231, 0.0000, 0.55, 0.082, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 1.1230, 1.2212, 0.0231, 0.0000, 0.65, 0.012, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 1.2212, 1.2212, 0.0231, 0.0000, 0.75, 0.129, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 1.3212, 1.2212, 0.0231, 0.0000, 0.85, 0.034, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 1.3923, 1.2212, 0.0131, 0.2344, 0.95, 0.001, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Call, 1.3455, 1.2212, 0.0000, 0.0000, 1.00, 0.127, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 0.9123, 1.2212, 0.0231, 0.0000, 0.25, 0.301, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 0.9234, 1.2212, 0.0231, 0.0000, 0.35, 0.111, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 0.9783, 1.2212, 0.0231, 0.0000, 0.45, 0.071, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 1.0000, 1.2212, 0.0231, 0.0000, 0.55, 0.082, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 1.1230, 1.2212, 0.0231, 0.0000, 0.65, 0.012, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 1.2212, 1.2212, 0.0231, 0.0000, 0.75, 0.129, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 1.3212, 1.2212, 0.0231, 0.0000, 0.85, 0.034, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 1.3923, 1.2212, 0.0131, 0.2344, 0.95, 0.001, 0.0, 0.0), + new EuropeanOptionData( Option.Type.Put, 1.3455, 1.2212, 0.0000, 0.0000, 1.00, 0.127, 0.0, 0.0), + // extreme case: zero vol + new EuropeanOptionData( Option.Type.Put, 1.3455, 1.2212, 0.0000, 0.0000, 0.50, 0.000, 0.0, 0.0), + // extreme case: zero strike + new EuropeanOptionData( Option.Type.Put, 0.0000, 1.2212, 0.0000, 0.0000, 1.50, 0.133, 0.0, 0.0), + // extreme case: zero strike+zero vol + new EuropeanOptionData( Option.Type.Put, 0.0000, 1.2212, 0.0000, 0.0000, 1.00, 0.133, 0.0, 0.0), + }; + + DayCounter dc = new Actual360(); + Calendar calendar = new TARGET(); + Date today = Date.Today; + + // Start setup of market data + + double discFor =0.0; + double discDom =0.0; + double implVol =0.0; + double expectedVal =0.0; + double calculatedVal =0.0; + double error =0.0; + + SimpleQuote spotQuote = new SimpleQuote(0.0); + Handle spotHandle = new Handle(spotQuote); + + SimpleQuote qQuote = new SimpleQuote(0.0); + Handle qHandle = new Handle(qQuote); + YieldTermStructure qTS = new FlatForward(today, qHandle, dc); + + SimpleQuote rQuote = new SimpleQuote(0.0); + Handle rHandle = new Handle(qQuote); + YieldTermStructure rTS = new FlatForward(today, rHandle, dc); + + SimpleQuote volQuote = new SimpleQuote(0.0); + Handle volHandle = new Handle(volQuote); + BlackVolTermStructure volTS = new BlackConstantVol(today, calendar, volHandle, dc); + + BlackScholesMertonProcess stochProcess; + IPricingEngine engine; + StrikedTypePayoff payoff; + Date exDate; + Exercise exercise; + // Setup of market data finished + + double tolerance=1.0e-10; + + for(int i=0; i(qTS), + new Handle(rTS), + new Handle(volTS)); + + engine = new AnalyticEuropeanEngine(stochProcess); + + EuropeanOption option = new EuropeanOption(payoff, exercise); + option.setPricingEngine(engine); + + calculatedVal=myCalc.deltaFromStrike(values[i].strike); + expectedVal=option.delta()-option.NPV()/spotQuote.value(); + error=Math.Abs(expectedVal-calculatedVal); + + if(error>tolerance) + { + Assert.Fail("\n Premium-adjusted spot delta test failed. \n" + + "Calculated Delta: " + calculatedVal + "\n" + + "Expected Value: " + expectedVal + "\n" + + "Error: "+ error); + } + + myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaFwd); + + calculatedVal=myCalc.deltaFromStrike(values[i].strike); + expectedVal=expectedVal/discFor; // Premium adjusted Fwd Delta is PA spot without discount + error=Math.Abs(expectedVal-calculatedVal); + + if(error>tolerance) + { + Assert.Fail("\n Premium-adjusted forward delta test failed. \n" + + "Calculated Delta: " + calculatedVal + "\n" + + "Expected Value: " + expectedVal + "\n" + + "Error: "+ error); + } + + + // Test consistency with BlackScholes Calculator for Spot Delta + myCalc.setDeltaType(DeltaVolQuote.DeltaType.Spot); + + calculatedVal=myCalc.deltaFromStrike(values[i].strike); + expectedVal=option.delta(); + error=Math.Abs(calculatedVal-expectedVal); + + if(error>tolerance) + { + Assert.Fail("\n spot delta in BlackDeltaCalculator differs from delta in BlackScholesCalculator. \n" + + "Calculated Value: " + calculatedVal + "\n" + + "Expected Value: " + expectedVal + "\n" + + "Error: " + error); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testPutCallParity() + { + // Testing put-call parity for deltas + + // Test for put call parity between put and call deltas. + + SavedSettings backup = new SavedSettings(); + + /* The data below are from + "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 + pag 11-16 + */ + + EuropeanOptionData[] values = { + // pag 2-8 + // type, strike, spot, q, r, t, vol, value, tol + new EuropeanOptionData( Option.Type.Call, 65.00, 60.00, 0.00, 0.08, 0.25, 0.30, 2.1334, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 95.00, 100.00, 0.05, 0.10, 0.50, 0.20, 2.4648, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 19.00, 19.00, 0.10, 0.10, 0.75, 0.28, 1.7011, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 19.00, 19.00, 0.10, 0.10, 0.75, 0.28, 1.7011, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 1.60, 1.56, 0.08, 0.06, 0.50, 0.12, 0.0291, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 70.00, 75.00, 0.05, 0.10, 0.50, 0.35, 4.0870, 1.0e-4), + // pag 24 + new EuropeanOptionData( Option.Type.Call, 100.00, 90.00, 0.10, 0.10, 0.10, 0.15, 0.0205, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.15, 1.8734, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.15, 9.9413, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 90.00, 0.10, 0.10, 0.10, 0.25, 0.3150, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.25, 3.1217, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.25, 10.3556, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 90.00, 0.10, 0.10, 0.10, 0.35, 0.9474, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.35, 4.3693, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.35, 11.1381, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 90.00, 0.10, 0.10, 0.50, 0.15, 0.8069, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.15, 4.0232, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.15, 10.5769, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 90.00, 0.10, 0.10, 0.50, 0.25, 2.7026, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.25, 6.6997, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.25, 12.7857, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 90.00, 0.10, 0.10, 0.50, 0.35, 4.9329, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.35, 9.3679, 1.0e-4), + new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.35, 15.3086, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 90.00, 0.10, 0.10, 0.10, 0.15, 9.9210, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 100.00, 0.10, 0.10, 0.10, 0.15, 1.8734, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 110.00, 0.10, 0.10, 0.10, 0.15, 0.0408, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 90.00, 0.10, 0.10, 0.10, 0.25, 10.2155, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 100.00, 0.10, 0.10, 0.10, 0.25, 3.1217, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 110.00, 0.10, 0.10, 0.10, 0.25, 0.4551, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 90.00, 0.10, 0.10, 0.10, 0.35, 10.8479, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 100.00, 0.10, 0.10, 0.10, 0.35, 4.3693, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 110.00, 0.10, 0.10, 0.10, 0.35, 1.2376, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 90.00, 0.10, 0.10, 0.50, 0.15, 10.3192, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 100.00, 0.10, 0.10, 0.50, 0.15, 4.0232, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 110.00, 0.10, 0.10, 0.50, 0.15, 1.0646, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 90.00, 0.10, 0.10, 0.50, 0.25, 12.2149, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 100.00, 0.10, 0.10, 0.50, 0.25, 6.6997, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 110.00, 0.10, 0.10, 0.50, 0.25, 3.2734, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 90.00, 0.10, 0.10, 0.50, 0.35, 14.4452, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 100.00, 0.10, 0.10, 0.50, 0.35, 9.3679, 1.0e-4), + new EuropeanOptionData( Option.Type.Put, 100.00, 110.00, 0.10, 0.10, 0.50, 0.35, 5.7963, 1.0e-4), + // pag 27 + new EuropeanOptionData( Option.Type.Call, 40.00, 42.00, 0.08, 0.04, 0.75, 0.35, 5.0975, 1.0e-4) + }; + + DayCounter dc = new Actual360(); + Calendar calendar = new TARGET(); + Date today = Date.Today; + + double discFor =0.0; + double discDom =0.0; + double implVol =0.0; + double deltaCall =0.0; + double deltaPut =0.0; + double expectedDiff =0.0; + double calculatedDiff =0.0; + double error =0.0; + double forward =0.0; + + SimpleQuote spotQuote = new SimpleQuote(0.0); + + SimpleQuote qQuote = new SimpleQuote(0.0); + Handle qHandle = new Handle(qQuote); + YieldTermStructure qTS = new FlatForward(today, qHandle, dc); + + SimpleQuote rQuote = new SimpleQuote(0.0); + Handle rHandle = new Handle(qQuote); + YieldTermStructure rTS = new FlatForward(today, rHandle, dc); + + SimpleQuote volQuote = new SimpleQuote(0.0); + Handle volHandle = new Handle(volQuote); + BlackVolTermStructure volTS = new BlackConstantVol(today, calendar, volHandle, dc); + + StrikedTypePayoff payoff; + Date exDate; + Exercise exercise; + + double tolerance=1.0e-10; + + for(int i=0; itolerance) + { + Assert.Fail("\n Put-call parity failed for spot delta. \n" + + "Calculated Call Delta: " + deltaCall + "\n" + + "Calculated Put Delta: " + deltaPut + "\n" + + "Expected Difference: " + expectedDiff + "\n" + + "Calculated Difference: " + calculatedDiff); + } + myCalc.setDeltaType(DeltaVolQuote.DeltaType.Fwd); + + deltaCall=myCalc.deltaFromStrike(values[i].strike); + myCalc.setOptionType(Option.Type.Put); + deltaPut=myCalc.deltaFromStrike(values[i].strike); + myCalc.setOptionType(Option.Type.Call); + + expectedDiff=1.0; + calculatedDiff=deltaCall-deltaPut; + error=Math.Abs(expectedDiff-calculatedDiff); + + if(error>tolerance) + { + Assert.Fail("\n Put-call parity failed for forward delta. \n" + + "Calculated Call Delta: " + deltaCall + "\n" + + "Calculated Put Delta: " + deltaPut + "\n" + + "Expected Difference: " + expectedDiff + "\n" + + "Calculated Difference: " + calculatedDiff ); + } + + myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaSpot); + + deltaCall=myCalc.deltaFromStrike(values[i].strike); + myCalc.setOptionType(Option.Type.Put); + deltaPut=myCalc.deltaFromStrike(values[i].strike); + myCalc.setOptionType(Option.Type.Call); + + expectedDiff=discFor*values[i].strike/forward; + calculatedDiff=deltaCall-deltaPut; + error=Math.Abs(expectedDiff-calculatedDiff); + + if(error>tolerance) + { + Assert.Fail("\n Put-call parity failed for premium-adjusted spot delta. \n" + + "Calculated Call Delta: " + deltaCall + "\n" + + "Calculated Put Delta: " + deltaPut + "\n" + + "Expected Difference: " + expectedDiff + "\n" + + "Calculated Difference: " + calculatedDiff); + } + + myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaFwd); + + deltaCall=myCalc.deltaFromStrike(values[i].strike); + myCalc.setOptionType(Option.Type.Put); + deltaPut=myCalc.deltaFromStrike(values[i].strike); + myCalc.setOptionType(Option.Type.Call); + + expectedDiff = values[i].strike/forward; + calculatedDiff=deltaCall-deltaPut; + error=Math.Abs(expectedDiff-calculatedDiff); + + if(error>tolerance) + { + Assert.Fail("\n Put-call parity failed for premium-adjusted forward delta. \n" + + "Calculated Call Delta: " + deltaCall + "\n" + + "Calculated Put Delta: " + deltaPut + "\n" + + "Expected Difference: " + expectedDiff + "\n" + + "Calculated Difference: " + calculatedDiff); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testAtmCalcs() + { + // Testing delta-neutral ATM quotations + SavedSettings backup = new SavedSettings(); + + DeltaData[] values = { + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Spot, 1.421, 0.997306, 0.992266, 0.1180654, 1.608080, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaSpot, 1.421, 0.997306, 0.992266, 0.1180654, 1.600545, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Fwd, 1.421, 0.997306, 0.992266, 0.1180654, 1.609029, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaFwd, 1.421, 0.997306, 0.992266, 0.1180654, 1.601550, 0.15), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Spot, 122.121, 0.9695434,0.9872347, 0.0887676, 119.8031, 0.67), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaSpot, 122.121, 0.9695434,0.9872347, 0.0887676, 117.7096, 0.67), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Fwd, 122.121, 0.9695434,0.9872347, 0.0887676, 120.0592, 0.67), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.PaFwd, 122.121, 0.9695434,0.9872347, 0.0887676, 118.0532, 0.67), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.Spot, 3.4582, 0.99979, 0.9250616, 0.3199034, 4.964924, -0.821), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.PaSpot, 3.4582, 0.99979, 0.9250616, 0.3199034, 3.778327, -0.821), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.Fwd, 3.4582, 0.99979, 0.9250616, 0.3199034, 4.51896, -0.821), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.PaFwd, 3.4582, 0.99979, 0.9250616, 0.3199034, 3.65728, -0.821), + // Data taken from Castagnas "FX Options and Smile Risk" (Wiley 2009) + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.Spot, 103.00, 0.99482, 0.98508, 0.07247845, 97.47, -0.25), + new DeltaData(Option.Type.Put, DeltaVolQuote.DeltaType.PaSpot, 103.00, 0.99482, 0.98508, 0.07247845, 97.22, -0.25), + // Extreme case: zero vol, ATM Fwd strike + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Fwd, 103.00, 0.99482, 0.98508, 0.0, 101.0013,0.5), + new DeltaData(Option.Type.Call, DeltaVolQuote.DeltaType.Spot, 103.00, 0.99482, 0.98508, 0.0, 101.0013,0.99482*0.5) + }; + + DeltaVolQuote.DeltaType currDt; + double currSpot; + double currdDf; + double currfDf; + double currStdDev; + double expected; + double calculated; + double error; + double tolerance=1.0e-2; // not that small, but sufficient for strikes + double currAtmStrike; + double currCallDelta; + double currPutDelta; + double currFwd; + + for (int i=0; i< values.Length; i++) + { + + currDt =values[i].dt; + currSpot =values[i].spot; + currdDf =values[i].dDf; + currfDf =values[i].fDf; + currStdDev =values[i].stdDev; + currFwd =currSpot*currfDf/currdDf; + + BlackDeltaCalculator myCalc = new BlackDeltaCalculator(Option.Type.Call, currDt, currSpot, currdDf, + currfDf, currStdDev); + + currAtmStrike=myCalc.atmStrike(DeltaVolQuote.AtmType.AtmDeltaNeutral); + currCallDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Put); + currPutDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Call); + + expected =0.0; + calculated =currCallDelta+currPutDelta; + error =Math.Abs(calculated-expected); + + if(error>tolerance) + { + Assert.Fail("\n Delta neutrality failed for spot delta in Delta Calculator. \n" + + "Iteration: "+ i + "\n" + + "Calculated Delta Sum: " + calculated + "\n" + + "Expected Delta Sum: " + expected + "\n" + + "Error: " + error); + } + + myCalc.setDeltaType(DeltaVolQuote.DeltaType.Fwd); + currAtmStrike=myCalc.atmStrike(DeltaVolQuote.AtmType.AtmDeltaNeutral); + currCallDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Put); + currPutDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Call); + + expected =0.0; + calculated =currCallDelta+currPutDelta; + error =Math.Abs(calculated-expected); + + if(error>tolerance) + { + Assert.Fail("\n Delta neutrality failed for forward delta in Delta Calculator. \n" + + "Iteration: " + i + "\n" + + "Calculated Delta Sum: " + calculated + "\n" + + "Expected Delta Sum: " + expected + "\n" + + "Error: " + error); + } + + myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaSpot); + currAtmStrike=myCalc.atmStrike(DeltaVolQuote.AtmType.AtmDeltaNeutral); + currCallDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Put); + currPutDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Call); + + expected =0.0; + calculated =currCallDelta+currPutDelta; + error =Math.Abs(calculated-expected); + + if(error>tolerance) + { + Assert.Fail("\n Delta neutrality failed for premium-adjusted spot delta in Delta Calculator. \n" + + "Iteration: " + i + "\n" + + "Calculated Delta Sum: " + calculated + "\n" + + "Expected Delta Sum: " + expected + "\n" + + "Error: " + error); + } + + + myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaFwd); + currAtmStrike=myCalc.atmStrike(DeltaVolQuote.AtmType.AtmDeltaNeutral); + currCallDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Put); + currPutDelta=myCalc.deltaFromStrike(currAtmStrike); + myCalc.setOptionType(Option.Type.Call); + + expected =0.0; + calculated =currCallDelta+currPutDelta; + error =Math.Abs(calculated-expected); + + if(error>tolerance) + { + Assert.Fail("\n Delta neutrality failed for premium-adjusted forward delta in Delta Calculator. \n" + + "Iteration: " + i + "\n" + + "Calculated Delta Sum: " + calculated + "\n" + + "Expected Delta Sum: " + expected + "\n" + + "Error: " + error); + } + + // Test ATM forward Calculations + calculated=myCalc.atmStrike(DeltaVolQuote.AtmType.AtmFwd); + expected=currFwd; + error=Math.Abs(expected-calculated); + + if(error>tolerance) + { + Assert.Fail("\n Atm forward test failed. \n" + + "Calculated Value: " + calculated + "\n" + + "Expected Value: " + expected + "\n" + + "Error: " + error); + } + + // Test ATM 0.50 delta calculations + myCalc.setDeltaType(DeltaVolQuote.DeltaType.Fwd); + double atmFiftyStrike=myCalc.atmStrike(DeltaVolQuote.AtmType.AtmPutCall50); + calculated=Math.Abs(myCalc.deltaFromStrike(atmFiftyStrike)); + expected=0.50; + error=Math.Abs(expected-calculated); + + if(error>tolerance) + { + Assert.Fail("\n Atm 0.50 delta strike test failed. \n" + + "Iteration:" + i + "\n" + + "Calculated Value: " + calculated + "\n" + + "Expected Value: " + expected + "\n" + + "Error: " + error); + } + } + } + } +} diff --git a/Test/T_BlackFormula.cs b/Test/T_BlackFormula.cs index 577132b11..7c472d185 100644 --- a/Test/T_BlackFormula.cs +++ b/Test/T_BlackFormula.cs @@ -61,5 +61,73 @@ public void testBachelierImpliedVol() } return; } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testChambersImpliedVol() + { + // Testing Chambers-Nawalkha implied vol approximation + + Option.Type[] types = {Option.Type.Call, Option.Type.Put}; + double[] displacements = {0.0000, 0.0010, 0.0050, 0.0100, 0.0200}; + double[] forwards = {-0.0010, 0.0000, 0.0050, 0.0100, 0.0200, 0.0500}; + double[] strikes = {-0.0100, -0.0050, -0.0010, 0.0000, 0.0010, 0.0050,0.0100, 0.0200, 0.0500, 0.1000}; + double[] stdDevs = {0.10, 0.15, 0.20, 0.30, 0.50, 0.60, 0.70,0.80, 1.00, 1.50, 2.00}; + double[] discounts = {1.00, 0.95, 0.80, 1.10}; + + double tol = 5.0E-4; + + for (int i1 = 0; i1 < types.Length; ++i1) + { + for (int i2 = 0; i2 < displacements.Length; ++i2) + { + for (int i3 = 0; i3 < forwards.Length; ++i3) + { + for (int i4 = 0; i4 < strikes.Length; ++i4) + { + for (int i5 = 0; i5 < stdDevs.Length; ++i5) + { + for (int i6 = 0; i6 < discounts.Length; ++i6) + { + if (forwards[i3] + displacements[i2] > 0.0 && + strikes[i4] + displacements[i2] > 0.0) + { + double premium = Utils.blackFormula( + types[i1], strikes[i4], forwards[i3], + stdDevs[i5], discounts[i6], + displacements[i2]); + double atmPremium = Utils.blackFormula( + types[i1], forwards[i3], forwards[i3], + stdDevs[i5], discounts[i6], + displacements[i2]); + double iStdDev = Utils.blackFormulaImpliedStdDevChambers( + types[i1], strikes[i4], forwards[i3], + premium, atmPremium, discounts[i6], + displacements[i2]); + double moneyness = (strikes[i4] + displacements[i2]) / + (forwards[i3] + displacements[i2]); + if(moneyness > 1.0) moneyness = 1.0 / moneyness; + double error = (iStdDev - stdDevs[i5]) / stdDevs[i5] * moneyness; + if(error > tol) + Assert.Fail("Failed to verify Chambers-Nawalkha approximation for " + + types[i1] + + " displacement=" + displacements[i2] + + " forward=" + forwards[i3] + + " strike=" + strikes[i4] + + " discount=" + discounts[i6] + + " stddev=" + stdDevs[i5] + + " result=" + iStdDev + + " exceeds maximum error tolerance"); + } + } + } + } + } + } + } + } } } diff --git a/Test/T_Bonds.cs b/Test/T_Bonds.cs index 18bb16af6..169cf38c3 100644 --- a/Test/T_Bonds.cs +++ b/Test/T_Bonds.cs @@ -563,7 +563,7 @@ public void testCachedFixed() } // varying coupons - InitializedList couponRates = new InitializedList(4); + List couponRates = new InitializedList(4); couponRates[0] = 0.02875; couponRates[1] = 0.03; couponRates[2] = 0.03125; @@ -703,7 +703,7 @@ public void testCachedFloating() } // varying spread - InitializedList spreads = new InitializedList(4); + List spreads = new InitializedList(4); spreads[0] = 0.001; spreads[1] = 0.0012; spreads[2] = 0.0014; @@ -755,7 +755,7 @@ public void testBrazilianCached() Settings.setEvaluationDate(today); // NTN-F maturity dates - InitializedList maturityDates = new InitializedList(6); + List maturityDates = new InitializedList(6); maturityDates[0] = new Date(1, Month.January, 2008); maturityDates[1] = new Date(1, Month.January, 2010); maturityDates[2] = new Date(1, Month.July, 2010); @@ -764,7 +764,7 @@ public void testBrazilianCached() maturityDates[5] = new Date(1, Month.January, 2017); // NTN-F yields - InitializedList yields = new InitializedList(6); + List yields = new InitializedList(6); yields[0] = 0.114614; yields[1] = 0.105726; yields[2] = 0.105328; @@ -773,7 +773,7 @@ public void testBrazilianCached() yields[5] = 0.102948; // NTN-F prices - InitializedList prices = new InitializedList(6); + List prices = new InitializedList(6); prices[0] = 1034.63031372; prices[1] = 1030.09919487; prices[2] = 1029.98307160; @@ -787,7 +787,7 @@ public void testBrazilianCached() // The tolerance is high because Andima truncate yields double tolerance = 1.0e-4; - InitializedList couponRates = new InitializedList(1); + List couponRates = new InitializedList(1); couponRates[0] = new InterestRate(0.1, new Thirty360(), Compounding.Compounded, Frequency.Annual); for (int bondIndex = 0; bondIndex < maturityDates.Count; bondIndex++) diff --git a/Test/T_CPISwap.cs b/Test/T_CPISwap.cs new file mode 100644 index 000000000..7dc7e9f65 --- /dev/null +++ b/Test/T_CPISwap.cs @@ -0,0 +1,517 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_CPISwap + { + internal struct Datum + { + public Date date; + public double rate; + + public Datum( Date d, double r ) + { + date = d; + rate = r; + } + } + + private class CommonVars + { + private List> makeHelpers( Datum[] iiData, int N, + ZeroInflationIndex ii, Period observationLag, + Calendar calendar, + BusinessDayConvention bdc, + DayCounter dc ) + { + List> instruments = new List>(); + for ( int i = 0; i < N; i++ ) + { + Date maturity = iiData[i].date; + Handle quote = new Handle( new SimpleQuote( iiData[i].rate / 100.0 ) ); + BootstrapHelper anInstrument = new ZeroCouponInflationSwapHelper( quote, observationLag, maturity, + calendar, bdc, dc, ii ); + instruments.Add( anInstrument ); + } + return instruments; + } + + // common data + public int length; + public Date startDate; + public double volatility; + + public Frequency frequency; + public List nominals; + public Calendar calendar; + public BusinessDayConvention convention; + public int fixingDays; + public Date evaluationDate; + public int settlementDays; + public Date settlement; + public Period observationLag, contractObservationLag; + public InterpolationType contractObservationInterpolation; + public DayCounter dcZCIIS, dcNominal; + public List zciisD; + public List zciisR; + public UKRPI ii; + public RelinkableHandle hii; + public int zciisDataLength; + + public RelinkableHandle nominalUK; + public RelinkableHandle cpiUK; + public RelinkableHandle hcpi; + + + // cleanup + + public SavedSettings backup; + public IndexHistoryCleaner cleaner; + + // setup + public CommonVars() + { + backup = new SavedSettings(); + cleaner = new IndexHistoryCleaner(); + nominalUK = new RelinkableHandle(); + cpiUK = new RelinkableHandle(); + hcpi = new RelinkableHandle(); + zciisD = new List(); + zciisR = new List(); + hii = new RelinkableHandle(); + + nominals = new InitializedList( 1, 1000000 ); + // option variables + frequency = Frequency.Annual; + // usual setup + volatility = 0.01; + length = 7; + calendar = new UnitedKingdom(); + convention = BusinessDayConvention.ModifiedFollowing; + Date today = new Date( 25, Month.November, 2009 ); + evaluationDate = calendar.adjust( today ); + Settings.setEvaluationDate( evaluationDate ); + settlementDays = 0; + fixingDays = 0; + settlement = calendar.advance( today, settlementDays, TimeUnit.Days ); + startDate = settlement; + dcZCIIS = new ActualActual(); + dcNominal = new ActualActual(); + + // uk rpi index + // fixing data + Date from = new Date( 20, Month.July, 2007 ); + Date to = new Date( 20, Month.November, 2009 ); + Schedule rpiSchedule = new MakeSchedule().from( from ).to( to ) + .withTenor( new Period( 1, TimeUnit.Months ) ) + .withCalendar( new UnitedKingdom() ) + .withConvention( BusinessDayConvention.ModifiedFollowing ).value(); + double[] fixData = { + 206.1, 207.3, 208.0, 208.9, 209.7, 210.9, + 209.8, 211.4, 212.1, 214.0, 215.1, 216.8, + 216.5, 217.2, 218.4, 217.7, 216, + 212.9, 210.1, 211.4, 211.3, 211.5, + 212.8, 213.4, 213.4, 213.4, 214.4, + -999.0, -999.0}; + + // link from cpi index to cpi TS + bool interp = false;// this MUST be false because the observation lag is only 2 months + // for ZCIIS; but not for contract if the contract uses a bigger lag. + ii = new UKRPI( interp, hcpi ); + for ( int i = 0; i < rpiSchedule.Count; i++ ) + { + ii.addFixing( rpiSchedule[i], fixData[i], true );// force overwrite in case multiple use + } + + Datum[] nominalData = + { + new Datum( new Date(26, Month.November, 2009), 0.475 ), + new Datum( new Date(2, Month.December, 2009), 0.47498 ), + new Datum( new Date(29, Month.December, 2009), 0.49988 ), + new Datum( new Date(25, Month.February, 2010), 0.59955) , + new Datum( new Date(18, Month.March, 2010), 0.65361), + new Datum( new Date(25, Month.May, 2010), 0.82830 ), + new Datum( new Date(17, Month.June, 2010), 0.7 ), + new Datum( new Date(16, Month.September, 2010), 0.78960), + new Datum( new Date(16, Month.December, 2010), 0.93762 ), + new Datum( new Date(17, Month.March, 2011), 1.12037 ), + new Datum( new Date(22, Month.September, 2011),1.52011), + new Datum( new Date(25, Month.November, 2011), 1.78399), + new Datum( new Date(26, Month.November, 2012), 2.41170), + new Datum( new Date(25, Month.November, 2013), 2.83935), + new Datum( new Date(25, Month.November, 2014), 3.12888), + new Datum( new Date(25, Month.November, 2015), 3.34298), + new Datum( new Date(25, Month.November, 2016), 3.50632), + new Datum( new Date(27, Month.November, 2017), 3.63666), + new Datum( new Date(26, Month.November, 2018), 3.74723), + new Datum( new Date(25, Month.November, 2019), 3.83988), + new Datum( new Date(25, Month.November, 2021), 4.00508), + new Datum( new Date(25, Month.November, 2024), 4.16042), + new Datum( new Date(26, Month.November, 2029), 4.15577), + new Datum( new Date(27, Month.November, 2034), 4.04933), + new Datum( new Date(25, Month.November, 2039), 3.95217), + new Datum( new Date(25, Month.November, 2049), 3.80932), + new Datum( new Date(25, Month.November, 2059), 3.80849), + new Datum( new Date(25, Month.November, 2069), 3.72677), + new Datum( new Date(27, Month.November, 2079), 3.63082 ) + + }; + int nominalDataLength = 30 - 1; + + List nomD = new List(); + List nomR = new List(); + for ( int i = 0; i < nominalDataLength; i++ ) + { + nomD.Add( nominalData[i].date ); + nomR.Add( nominalData[i].rate / 100.0 ); + } + YieldTermStructure nominal = new InterpolatedZeroCurve( nomD, nomR, dcNominal ); + nominalUK.linkTo( nominal ); + + // now build the zero inflation curve + observationLag = new Period( 2, TimeUnit.Months ); + contractObservationLag = new Period( 3, TimeUnit.Months ); + contractObservationInterpolation = InterpolationType.Flat; + + Datum[] zciisData = + { + new Datum( new Date(25, Month.November, 2010), 3.0495 ), + new Datum( new Date(25, Month.November, 2011), 2.93 ), + new Datum( new Date(26, Month.November, 2012), 2.9795 ), + new Datum( new Date(25, Month.November, 2013), 3.029 ), + new Datum( new Date(25, Month.November, 2014), 3.1425 ), + new Datum( new Date(25, Month.November, 2015), 3.211 ), + new Datum( new Date(25, Month.November, 2016), 3.2675 ), + new Datum( new Date(25, Month.November, 2017), 3.3625 ), + new Datum( new Date(25, Month.November, 2018), 3.405 ), + new Datum( new Date(25, Month.November, 2019), 3.48 ), + new Datum( new Date(25, Month.November, 2021), 3.576 ), + new Datum( new Date(25, Month.November, 2024), 3.649 ), + new Datum( new Date(26, Month.November, 2029), 3.751 ), + new Datum( new Date(27, Month.November, 2034), 3.77225 ), + new Datum( new Date(25, Month.November, 2039), 3.77 ), + new Datum( new Date(25, Month.November, 2049), 3.734 ), + new Datum( new Date(25, Month.November, 2059), 3.714 ) + }; + zciisDataLength = 17; + for ( int i = 0; i < zciisDataLength; i++ ) + { + zciisD.Add( zciisData[i].date ); + zciisR.Add( zciisData[i].rate ); + } + + // now build the helpers ... + List> helpers = makeHelpers( zciisData, zciisDataLength, ii, + observationLag, calendar, convention, dcZCIIS ); + + // we can use historical or first ZCIIS for this + // we know historical is WAY off market-implied, so use market implied flat. + double baseZeroRate = zciisData[0].rate / 100.0; + PiecewiseZeroInflationCurve pCPIts = new PiecewiseZeroInflationCurve( + evaluationDate, calendar, dcZCIIS, observationLag, ii.frequency(), ii.interpolated(), baseZeroRate, + new Handle( nominalUK ), helpers ); + pCPIts.recalculate(); + cpiUK.linkTo( pCPIts ); + + // make sure that the index has the latest zero inflation term structure + hcpi.linkTo( pCPIts ); + + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void consistency() + { + // check inflation leg vs calculation directly from inflation TS + CommonVars common = new CommonVars(); + + // ZeroInflationSwap aka CPISwap + CPISwap.Type type = CPISwap.Type.Payer; + double nominal = 1000000.0; + bool subtractInflationNominal = true; + // float+spread leg + double spread = 0.0; + DayCounter floatDayCount = new Actual365Fixed(); + BusinessDayConvention floatPaymentConvention = BusinessDayConvention.ModifiedFollowing; + int fixingDays = 0; + IborIndex floatIndex = new GBPLibor(new Period(6,TimeUnit.Months),common.nominalUK); + + // fixed x inflation leg + double fixedRate = 0.1;//1% would be 0.01 + double baseCPI = 206.1; // would be 206.13871 if we were interpolating + DayCounter fixedDayCount = new Actual365Fixed(); + BusinessDayConvention fixedPaymentConvention = BusinessDayConvention.ModifiedFollowing; + Calendar fixedPaymentCalendar = new UnitedKingdom(); + ZeroInflationIndex fixedIndex = common.ii; + Period contractObservationLag = common.contractObservationLag; + InterpolationType observationInterpolation = common.contractObservationInterpolation; + + // set the schedules + Date startDate = new Date(2, Month.October, 2007); + Date endDate = new Date(2, Month.October, 2052); + Schedule floatSchedule = new MakeSchedule().from(startDate).to(endDate) + .withTenor(new Period(6,TimeUnit.Months)) + .withCalendar(new UnitedKingdom()) + .withConvention(floatPaymentConvention) + .backwards().value(); + Schedule fixedSchedule = new MakeSchedule().from(startDate).to(endDate) + .withTenor(new Period(6,TimeUnit.Months)) + .withCalendar(new UnitedKingdom()) + .withConvention(BusinessDayConvention.Unadjusted) + .backwards().value(); + + + CPISwap zisV = new CPISwap(type, nominal, subtractInflationNominal, + spread, floatDayCount, floatSchedule, + floatPaymentConvention, fixingDays, floatIndex, + fixedRate, baseCPI, fixedDayCount, fixedSchedule, + fixedPaymentConvention, contractObservationLag, + fixedIndex, observationInterpolation); + Date asofDate = Settings.evaluationDate(); + + double[] floatFix = {0.06255,0.05975,0.0637,0.018425,0.0073438,-1,-1}; + double[] cpiFix = {211.4,217.2,211.4,213.4,-2,-2}; + for(int i=0;i asofDate) + { + testInfLegNPV += (zisV.leg(0))[i].amount()*common.nominalUK.link.discount(zicPayDate); + } + + CPICoupon zicV = zisV.cpiLeg()[i] as CPICoupon; + if (zicV != null) + { + diff = Math.Abs( zicV.rate() - (fixedRate*(zicV.indexFixing()/baseCPI)) ); + Assert.IsTrue(diff<1e-8, "failed "+i+"th coupon reconstruction as " + + (fixedRate*(zicV.indexFixing()/baseCPI)) + " vs rate = " + +zicV.rate() + ", with difference: " + diff); + } + } + + double error = Math.Abs(testInfLegNPV - zisV.legNPV(0).Value); + Assert.IsTrue( error < 1e-5, "failed manual inf leg NPV calc vs pricing engine: " + testInfLegNPV + " vs " + + zisV.legNPV(0)); + + diff = Math.Abs(1-zisV.NPV()/4191660.0); + #if QL_USE_INDEXED_COUPON + double max_diff = 1e-5; + #else + double max_diff = 3e-5; + #endif + Assert.IsTrue( diff < max_diff, "failed stored consistency value test, ratio = " + diff ); + + // remove circular refernce + common.hcpi.linkTo(null); + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void zciisconsistency() + { + CommonVars common = new CommonVars(); + + ZeroCouponInflationSwap.Type ztype = ZeroCouponInflationSwap.Type.Payer; + double nominal = 1000000.0; + Date startDate = new Date(common.evaluationDate); + Date endDate = new Date(25, Month.November, 2059); + Calendar cal = new UnitedKingdom(); + BusinessDayConvention paymentConvention = BusinessDayConvention.ModifiedFollowing; + DayCounter dummyDC = null, dc = new ActualActual(); + Period observationLag = new Period(2,TimeUnit.Months); + + double quote = 0.03714; + ZeroCouponInflationSwap zciis = new ZeroCouponInflationSwap(ztype, nominal, startDate, endDate, cal, + paymentConvention, dc, quote, common.ii, observationLag); + + // simple structure so simple pricing engine - most work done by index + DiscountingSwapEngine dse = new DiscountingSwapEngine(common.nominalUK); + + zciis.setPricingEngine(dse); + Assert.IsTrue(Math.Abs(zciis.NPV())<1e-3,"zciis does not reprice to zero"); + + List oneDate = new List(); + oneDate.Add(endDate); + Schedule schOneDate = new Schedule(oneDate, cal, paymentConvention); + + CPISwap.Type stype = CPISwap.Type.Payer; + double inflationNominal = nominal; + double floatNominal = inflationNominal * Math.Pow(1.0+quote,50); + bool subtractInflationNominal = true; + double dummySpread=0.0, dummyFixedRate=0.0; + int fixingDays = 0; + Date baseDate = startDate - observationLag; + double baseCPI = common.ii.fixing(baseDate); + + IborIndex dummyFloatIndex = new IborIndex(); + + CPISwap cS = new CPISwap(stype, floatNominal, subtractInflationNominal, dummySpread, dummyDC, schOneDate, + paymentConvention, fixingDays, dummyFloatIndex, + dummyFixedRate, baseCPI, dummyDC, schOneDate, paymentConvention, observationLag, + common.ii, InterpolationType.AsIndex, inflationNominal); + + cS.setPricingEngine(dse); + Assert.IsTrue(Math.Abs(cS.NPV())<1e-3,"CPISwap as ZCIIS does not reprice to zero"); + + for (int i=0; i<2; i++) + { + double cs = cS.legNPV(i).GetValueOrDefault(); + double z = zciis.legNPV(i).GetValueOrDefault(); + Assert.IsTrue(Math.Abs(cs - z)<1e-3, "zciis leg does not equal CPISwap leg"); + } + // remove circular refernce + common.hcpi.linkTo(null); + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void cpibondconsistency() + { + CommonVars common = new CommonVars(); + + // ZeroInflationSwap aka CPISwap + + CPISwap.Type type = CPISwap.Type.Payer; + double nominal = 1000000.0; + bool subtractInflationNominal = true; + // float+spread leg + double spread = 0.0; + DayCounter floatDayCount = new Actual365Fixed(); + BusinessDayConvention floatPaymentConvention = BusinessDayConvention.ModifiedFollowing; + int fixingDays = 0; + IborIndex floatIndex = new GBPLibor(new Period(6,TimeUnit.Months),common.nominalUK); + + // fixed x inflation leg + double fixedRate = 0.1;//1% would be 0.01 + double baseCPI = 206.1; // would be 206.13871 if we were interpolating + DayCounter fixedDayCount = new Actual365Fixed(); + BusinessDayConvention fixedPaymentConvention = BusinessDayConvention.ModifiedFollowing; + Calendar fixedPaymentCalendar = new UnitedKingdom(); + ZeroInflationIndex fixedIndex = common.ii; + Period contractObservationLag = common.contractObservationLag; + InterpolationType observationInterpolation = common.contractObservationInterpolation; + + // set the schedules + Date startDate = new Date(2, Month.October, 2007); + Date endDate = new Date(2, Month.October, 2052); + Schedule floatSchedule = new MakeSchedule().from(startDate).to(endDate) + .withTenor(new Period(6,TimeUnit.Months)) + .withCalendar(new UnitedKingdom()) + .withConvention(floatPaymentConvention) + .backwards().value(); + Schedule fixedSchedule = new MakeSchedule().from(startDate).to(endDate) + .withTenor(new Period(6,TimeUnit.Months)) + .withCalendar(new UnitedKingdom()) + .withConvention(BusinessDayConvention.Unadjusted) + .backwards().value(); + + CPISwap zisV = new CPISwap(type, nominal, subtractInflationNominal, + spread, floatDayCount, floatSchedule, + floatPaymentConvention, fixingDays, floatIndex, + fixedRate, baseCPI, fixedDayCount, fixedSchedule, + fixedPaymentConvention, contractObservationLag, + fixedIndex, observationInterpolation); + + double[] floatFix = {0.06255,0.05975,0.0637,0.018425,0.0073438,-1,-1}; + double[] cpiFix = {211.4,217.2,211.4,213.4,-2,-2}; + for(int i=0;i fixedRates = new InitializedList(1,fixedRate); + int settlementDays = 1;// cannot be zero! + bool growthOnly = true; + CPIBond cpiB = new CPIBond(settlementDays, nominal, growthOnly, + baseCPI, contractObservationLag, fixedIndex, + observationInterpolation, fixedSchedule, + fixedRates, fixedDayCount, fixedPaymentConvention); + + DiscountingBondEngine dbe = new DiscountingBondEngine(common.nominalUK); + cpiB.setPricingEngine(dbe); + + Assert.IsTrue(Math.Abs(cpiB.NPV() - zisV.legNPV(0).GetValueOrDefault())<1e-5, + "cpi bond does not equal equivalent cpi swap leg"); + // remove circular refernce + common.hcpi.linkTo(null); + } + } +} diff --git a/Test/T_Calendars.cs b/Test/T_Calendars.cs index 327fdc6a4..848a4576f 100644 --- a/Test/T_Calendars.cs +++ b/Test/T_Calendars.cs @@ -176,17 +176,41 @@ public void testUSSettlement() { Calendar c = new UnitedStates(UnitedStates.Market.Settlement); List hol = Calendar.holidayList(c, new Date(1, Month.January, 2004), - new Date(31, Month.December, 2005)); + new Date(31, Month.December, 2005)); + if ( hol.Count != expectedHol.Count ) + Assert.Fail( "there were " + expectedHol.Count + + " expected holidays, while there are " + hol.Count + + " calculated holidays" ); - for (int i = 0; i < Math.Min(hol.Count, expectedHol.Count); i++) { + for (int i = 0; i (); + expectedHol.Add(new Date(2,Month.January,1961)); + expectedHol.Add(new Date(22,Month.February,1961)); + expectedHol.Add(new Date(30,Month.May,1961)); + expectedHol.Add(new Date(4,Month.July,1961)); + expectedHol.Add(new Date(4,Month.September,1961)); + expectedHol.Add(new Date(10,Month.November,1961)); + expectedHol.Add(new Date(23,Month.November,1961)); + expectedHol.Add(new Date(25,Month.December,1961)); + + hol = Calendar.holidayList( c, new Date( 1, Month.January, 1961 ), new Date( 31, Month.December, 1961 ) ); if (hol.Count != expectedHol.Count) QAssert.Fail("there were " + expectedHol.Count + " expected holidays, while there are " + hol.Count + - " calculated holidays"); + " calculated holidays"); + + for ( int i = 0; i < hol.Count; i++ ) + { + if ( hol[i] != expectedHol[i] ) + Assert.Fail( "expected holiday was " + expectedHol[i] + + " while calculated holiday is " + hol[i] ); + } } #if QL_DOTNET_FRAMEWORK @@ -966,6 +990,145 @@ public void testKoreaStockExchange() { + " calculated holidays"); } +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testChinaSSE() + { + // Testing China Shanghai Stock Exchange holiday list + + List expectedHol = new List(); + + // China Shanghai Securities Exchange holiday list in the year 2014 + expectedHol.Add(new Date(1, Month.Jan, 2014)); + expectedHol.Add(new Date(31, Month.Jan, 2014)); + expectedHol.Add(new Date(3, Month.Feb, 2014)); + expectedHol.Add(new Date(4, Month.Feb, 2014)); + expectedHol.Add(new Date(5, Month.Feb, 2014)); + expectedHol.Add(new Date(6, Month.Feb, 2014)); + expectedHol.Add(new Date(7, Month.Apr, 2014)); + expectedHol.Add(new Date(1, Month.May, 2014)); + expectedHol.Add(new Date(2, Month.May, 2014)); + expectedHol.Add(new Date(2, Month.Jun, 2014)); + expectedHol.Add(new Date(8, Month.Sep, 2014)); + expectedHol.Add(new Date(1, Month.Oct, 2014)); + expectedHol.Add(new Date(2, Month.Oct, 2014)); + expectedHol.Add(new Date(3, Month.Oct, 2014)); + expectedHol.Add(new Date(6, Month.Oct, 2014)); + expectedHol.Add(new Date(7, Month.Oct, 2014)); + + // China Shanghai Securities Exchange holiday list in the year 2015 + expectedHol.Add(new Date(1, Month.Jan, 2015)); + expectedHol.Add(new Date(2, Month.Jan, 2015)); + expectedHol.Add(new Date(18,Month.Feb, 2015)); + expectedHol.Add(new Date(19,Month.Feb, 2015)); + expectedHol.Add(new Date(20,Month.Feb, 2015)); + expectedHol.Add(new Date(23,Month.Feb, 2015)); + expectedHol.Add(new Date(24,Month.Feb, 2015)); + expectedHol.Add(new Date(6, Month.Apr, 2015)); + expectedHol.Add(new Date(1, Month.May, 2015)); + expectedHol.Add(new Date(22,Month.Jun, 2015)); + expectedHol.Add(new Date(3, Month.Sep, 2015)); + expectedHol.Add(new Date(4, Month.Sep, 2015)); + expectedHol.Add(new Date(1, Month.Oct, 2015)); + expectedHol.Add(new Date(2, Month.Oct, 2015)); + expectedHol.Add(new Date(5, Month.Oct, 2015)); + expectedHol.Add(new Date(6, Month.Oct, 2015)); + expectedHol.Add(new Date(7, Month.Oct, 2015)); + + // China Shanghai Securities Exchange holiday list in the year 2016 + expectedHol.Add(new Date(1, Month.Jan, 2016)); + expectedHol.Add(new Date(8, Month.Feb, 2016)); + expectedHol.Add(new Date(9, Month.Feb, 2016)); + expectedHol.Add(new Date(10,Month.Feb, 2016)); + expectedHol.Add(new Date(11,Month.Feb, 2016)); + expectedHol.Add(new Date(12,Month.Feb, 2016)); + expectedHol.Add(new Date(4, Month.Apr, 2016)); + expectedHol.Add(new Date(2, Month.May, 2016)); + expectedHol.Add(new Date(9, Month.Jun, 2016)); + expectedHol.Add(new Date(10,Month.Jun, 2016)); + expectedHol.Add(new Date(15,Month.Sep, 2016)); + expectedHol.Add(new Date(16,Month.Sep, 2016)); + expectedHol.Add(new Date(3, Month.Oct, 2016)); + expectedHol.Add(new Date(4, Month.Oct, 2016)); + expectedHol.Add(new Date(5, Month.Oct, 2016)); + expectedHol.Add(new Date(6, Month.Oct, 2016)); + expectedHol.Add(new Date(7, Month.Oct, 2016)); + + + Calendar c = new China(China.Market.SSE); + List hol = Calendar.holidayList(c, new Date(1, Month.January, 2014), + new Date(31, Month.December, 2016)); + + for (int i = 0; i < Math.Min(hol.Count, expectedHol.Count); i++) + { + if (hol[i] != expectedHol[i]) + Assert.Fail("expected holiday was " + expectedHol[i] + + " while calculated holiday is " + hol[i]); + } + if (hol.Count != expectedHol.Count) + Assert.Fail("there were " + expectedHol.Count + + " expected holidays, while there are " + hol.Count + + " calculated holidays"); +} + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testChinaIB() + { + // Testing China Inter Bank working weekends list + List expectedWorkingWeekEnds = new List(); + + // China Inter Bank working weekends list in the year 2014 + expectedWorkingWeekEnds.Add(new Date(26,Month. Jan, 2014)); + expectedWorkingWeekEnds.Add(new Date(8, Month.Feb, 2014)); + expectedWorkingWeekEnds.Add(new Date(4, Month.May, 2014)); + expectedWorkingWeekEnds.Add(new Date(28,Month. Sep, 2014)); + expectedWorkingWeekEnds.Add(new Date(11,Month. Oct, 2014)); + + // China Inter Bank working weekends list in the year 2015 + expectedWorkingWeekEnds.Add(new Date(4, Month.Jan, 2015)); + expectedWorkingWeekEnds.Add(new Date(15,Month. Feb, 2015)); + expectedWorkingWeekEnds.Add(new Date(28,Month. Feb, 2015)); + expectedWorkingWeekEnds.Add(new Date(6, Month.Sep, 2015)); + expectedWorkingWeekEnds.Add(new Date(10,Month. Oct, 2015)); + + // China Inter Bank working weekends list in the year 2016 + expectedWorkingWeekEnds.Add(new Date(6, Month.Feb, 2016)); + expectedWorkingWeekEnds.Add(new Date(14,Month. Feb, 2016)); + expectedWorkingWeekEnds.Add(new Date(12,Month. Jun, 2016)); + expectedWorkingWeekEnds.Add(new Date(18,Month. Sep, 2016)); + expectedWorkingWeekEnds.Add(new Date(8, Month.Oct, 2016)); + expectedWorkingWeekEnds.Add(new Date(9, Month.Oct, 2016)); + + Calendar c = new China(China.Market.IB); + Date start = new Date(1, Month.Jan, 2014); + Date end = new Date(31, Month.Dec, 2016); + + int k = 0; + + while (start <= end) + { + if (c.isBusinessDay(start) && c.isWeekend(start.DayOfWeek)) + { + if (expectedWorkingWeekEnds[k] != start) + Assert.Fail("expected working weekend was " + expectedWorkingWeekEnds[k] + + " while calculated working weekend is " + start); + ++k; + } + ++start; + } + + if (k != (expectedWorkingWeekEnds.Count)) + Assert.Fail("there were " + expectedWorkingWeekEnds.Count + + " expected working weekends, while there are " + k + + " calculated holidays"); +} #if QL_DOTNET_FRAMEWORK [TestMethod()] #else diff --git a/Test/T_CapFlooredCoupon.cs b/Test/T_CapFlooredCoupon.cs new file mode 100644 index 000000000..28b338035 --- /dev/null +++ b/Test/T_CapFlooredCoupon.cs @@ -0,0 +1,509 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ + #if QL_DOTNET_FRAMEWORK + [TestClass()] + #endif + public class T_CapFlooredCoupon + { + private class CommonVars + { + // global data + public Date today, settlement, startDate; + public Calendar calendar; + public double nominal; + public List nominals; + public BusinessDayConvention convention; + public Frequency frequency; + public IborIndex index; + public int settlementDays, fixingDays; + public RelinkableHandle termStructure ; + //public List caps; + //public List floors; + public int length; + public double volatility; + + // cleanup + SavedSettings backup; + + // setup + public CommonVars() + { + termStructure = new RelinkableHandle(); + backup = new SavedSettings(); + length = 20; //years + volatility = 0.20; + nominal = 100.0; + nominals = new InitializedList(length,nominal); + frequency = Frequency.Annual; + index = new Euribor1Y(termStructure); + calendar = index.fixingCalendar(); + convention = BusinessDayConvention.ModifiedFollowing; + today = calendar.adjust(Date.Today); + Settings.setEvaluationDate(today); + settlementDays = 2; + fixingDays = 2; + settlement = calendar.advance(today,settlementDays,TimeUnit.Days); + startDate = settlement; + termStructure.linkTo(Utilities.flatRate(settlement,0.05, new ActualActual(ActualActual.Convention.ISDA))); + } + + // utilities + public List makeFixedLeg( Date sDate,int len) + { + Date endDate = calendar.advance( sDate, len, TimeUnit.Years, convention ); + Schedule schedule = new Schedule( sDate, endDate, new Period( frequency ), calendar, + convention, convention, DateGeneration.Rule.Forward, false); + List coupons = new InitializedList(len, 0.0); + return new FixedRateLeg(schedule) + .withCouponRates(coupons, new Thirty360()) + .withNotionals(nominals); + } + + public List makeFloatingLeg( Date sDate, int len, double gearing = 1.0, double spread = 0.0 ) + { + Date endDate = calendar.advance( sDate, len, TimeUnit.Years, convention ); + Schedule schedule = new Schedule( sDate, endDate, new Period( frequency ), calendar, + convention,convention,DateGeneration.Rule.Forward,false); + List gearingVector = new InitializedList(len, gearing); + List spreadVector = new InitializedList(len, spread); + return new IborLeg(schedule, index) + .withPaymentDayCounter(index.dayCounter()) + .withFixingDays(fixingDays) + .withGearings(gearingVector) + .withSpreads(spreadVector) + .withNotionals(nominals) + .withPaymentAdjustment(convention); + } + + public List makeCapFlooredLeg( Date sDate, int len, List caps, List floors, + double volatility,double gearing = 1.0,double spread = 0.0) + { + Date endDate = calendar.advance( sDate, len, TimeUnit.Years, convention ); + Schedule schedule = new Schedule( sDate, endDate, new Period( frequency ), calendar, + convention,convention,DateGeneration.Rule.Forward,false); + Handle vol = new Handle(new + ConstantOptionletVolatility(0, calendar, BusinessDayConvention.Following, volatility,new Actual365Fixed())); + IborCouponPricer pricer = new BlackIborCouponPricer(vol); + List gearingVector = new InitializedList(len, gearing); + List spreadVector = new InitializedList(len, spread); + + List iborLeg = new IborLeg(schedule, index) + .withFloors(floors) + .withPaymentDayCounter(index.dayCounter()) + .withFixingDays(fixingDays) + .withGearings(gearingVector) + .withSpreads(spreadVector) + .withCaps(caps) + .withNotionals(nominals) + .withPaymentAdjustment(convention); + Utils.setCouponPricer(iborLeg, pricer); + return iborLeg; + } + + public IPricingEngine makeEngine(double vols) + { + Handle vol = new Handle(new SimpleQuote(vols)); + return new BlackCapFloorEngine(termStructure, vol); + } + + public CapFloor makeCapFloor(CapFloorType type,List leg,double capStrike, + double floorStrike,double vol) + { + CapFloor result = null; + switch (type) + { + case CapFloorType.Cap: + result = new Cap(leg, new InitializedList(1, capStrike)); + break; + case CapFloorType.Floor: + result = new Floor(leg, new InitializedList(1, floorStrike)); + break; + case CapFloorType.Collar: + result = new Collar(leg,new InitializedList(1, capStrike), + new InitializedList(1, floorStrike)); + break; + default: + Utils.QL_FAIL("unknown cap/floor type"); + break; + } + result.setPricingEngine(makeEngine(vol)); + return result; + } + + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testLargeRates() + { + // Testing degenerate collared coupon + + CommonVars vars = new CommonVars(); + + /* A vanilla floating leg and a capped floating leg with strike + equal to 100 and floor equal to 0 must have (about) the same NPV + (depending on variance: option expiry and volatility) + */ + + List caps = new InitializedList(vars.length,100.0); + List floors = new InitializedList(vars.length,0.0); + double tolerance = 1e-10; + + // fixed leg with zero rate + List fixedLeg = vars.makeFixedLeg(vars.startDate,vars.length); + List floatLeg = vars.makeFloatingLeg(vars.startDate,vars.length); + List collaredLeg = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps,floors,vars.volatility); + + IPricingEngine engine = new DiscountingSwapEngine(vars.termStructure); + Swap vanillaLeg = new Swap(fixedLeg,floatLeg); + Swap collarLeg = new Swap(fixedLeg,collaredLeg); + vanillaLeg.setPricingEngine(engine); + collarLeg.setPricingEngine(engine); + double npvVanilla = vanillaLeg.NPV(); + double npvCollar = collarLeg.NPV(); + if ( Math.Abs( npvVanilla - npvCollar ) > tolerance ) + { + Assert.Fail("Lenght: " + vars.length + " y" + "\n" + + "Volatility: " + vars.volatility*100 + "%\n" + + "Notional: " + vars.nominal + "\n" + + "Vanilla floating leg NPV: " + vanillaLeg.NPV() + + "\n" + + "Collared floating leg NPV (strikes 0 and 100): " + + collarLeg.NPV() + + "\n" + + "Diff: " + Math.Abs(vanillaLeg.NPV()-collarLeg.NPV())); + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testDecomposition() + { + // Testing collared coupon against its decomposition + + CommonVars vars = new CommonVars(); + + double tolerance = 1e-12; + double npvVanilla,npvCappedLeg,npvFlooredLeg,npvCollaredLeg,npvCap,npvFloor,npvCollar; + double error; + double floorstrike = 0.05; + double capstrike = 0.10; + List caps = new InitializedList(vars.length,capstrike); + List caps0 = new List(); + List floors = new InitializedList(vars.length,floorstrike); + List floors0 = new List(); + double gearing_p = 0.5; + double spread_p = 0.002; + double gearing_n = -1.5; + double spread_n = 0.12; + // fixed leg with zero rate + List fixedLeg = vars.makeFixedLeg(vars.startDate,vars.length); + // floating leg with gearing=1 and spread=0 + List floatLeg = vars.makeFloatingLeg(vars.startDate,vars.length); + // floating leg with positive gearing (gearing_p) and spread<>0 + List floatLeg_p = vars.makeFloatingLeg(vars.startDate,vars.length,gearing_p,spread_p); + // floating leg with negative gearing (gearing_n) and spread<>0 + List floatLeg_n = vars.makeFloatingLeg(vars.startDate,vars.length,gearing_n,spread_n); + // Swap with null fixed leg and floating leg with gearing=1 and spread=0 + Swap vanillaLeg = new Swap(fixedLeg,floatLeg); + // Swap with null fixed leg and floating leg with positive gearing and spread<>0 + Swap vanillaLeg_p = new Swap(fixedLeg,floatLeg_p); + // Swap with null fixed leg and floating leg with negative gearing and spread<>0 + Swap vanillaLeg_n = new Swap(fixedLeg,floatLeg_n); + + IPricingEngine engine = new DiscountingSwapEngine(vars.termStructure); + vanillaLeg.setPricingEngine(engine); + vanillaLeg_p.setPricingEngine(engine); + vanillaLeg_n.setPricingEngine(engine); + + /* CAPPED coupon - Decomposition of payoff + Payoff = Nom * Min(rate,strike) * accrualperiod = + = Nom * [rate + Min(0,strike-rate)] * accrualperiod = + = Nom * rate * accrualperiod - Nom * Max(rate-strike,0) * accrualperiod = + = VanillaFloatingLeg - Call + */ + + // Case gearing = 1 and spread = 0 + List cappedLeg = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps,floors0,vars.volatility); + Swap capLeg = new Swap(fixedLeg,cappedLeg); + capLeg.setPricingEngine(engine); + Cap cap = new Cap(floatLeg, new InitializedList(1, capstrike)); + cap.setPricingEngine(vars.makeEngine(vars.volatility)); + npvVanilla = vanillaLeg.NPV(); + npvCappedLeg = capLeg.NPV(); + npvCap = cap.NPV(); + error = Math.Abs(npvCappedLeg - (npvVanilla-npvCap)); + if (error>tolerance) + { + Assert.Fail("\nCapped Leg: gearing=1, spread=0%, strike=" + capstrike*100 + + "%\n" + + " Capped Floating Leg NPV: " + npvCappedLeg + "\n" + + " Floating Leg NPV - Cap NPV: " + (npvVanilla - npvCap) + "\n" + + " Diff: " + error ); + } + + /* gearing = 1 and spread = 0 + FLOORED coupon - Decomposition of payoff + Payoff = Nom * Max(rate,strike) * accrualperiod = + = Nom * [rate + Max(0,strike-rate)] * accrualperiod = + = Nom * rate * accrualperiod + Nom * Max(strike-rate,0) * accrualperiod = + = VanillaFloatingLeg + Put + */ + + List flooredLeg = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps0,floors,vars.volatility); + Swap floorLeg = new Swap(fixedLeg,flooredLeg); + floorLeg.setPricingEngine(engine); + Floor floor = new Floor(floatLeg, new InitializedList(1, floorstrike)); + floor.setPricingEngine(vars.makeEngine(vars.volatility)); + npvFlooredLeg = floorLeg.NPV(); + npvFloor = floor.NPV(); + error = Math.Abs(npvFlooredLeg-(npvVanilla + npvFloor)); + if (error>tolerance) + { + Assert.Fail("Floored Leg: gearing=1, spread=0%, strike=" + floorstrike *100 + + "%\n" + + " Floored Floating Leg NPV: " + npvFlooredLeg + "\n" + + " Floating Leg NPV + Floor NPV: " + (npvVanilla + npvFloor) + "\n" + + " Diff: " + error ); + } + + /* gearing = 1 and spread = 0 + COLLARED coupon - Decomposition of payoff + Payoff = Nom * Min(strikem,Max(rate,strikeM)) * accrualperiod = + = VanillaFloatingLeg - Collar + */ + + List collaredLeg = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps,floors,vars.volatility); + Swap collarLeg = new Swap(fixedLeg,collaredLeg); + collarLeg.setPricingEngine(engine); + Collar collar = new Collar(floatLeg,new InitializedList(1, capstrike), + new InitializedList(1, floorstrike)); + collar.setPricingEngine(vars.makeEngine(vars.volatility)); + npvCollaredLeg = collarLeg.NPV(); + npvCollar = collar.NPV(); + error = Math.Abs(npvCollaredLeg -(npvVanilla - npvCollar)); + if (error>tolerance) + { + Assert.Fail("\nCollared Leg: gearing=1, spread=0%, strike=" + + floorstrike*100 + "% and " + capstrike*100 + "%\n" + + " Collared Floating Leg NPV: " + npvCollaredLeg + "\n" + + " Floating Leg NPV - Collar NPV: " + (npvVanilla - npvCollar) + "\n" + + " Diff: " + error ); + } + + /* gearing = a and spread = b + CAPPED coupon - Decomposition of payoff + Payoff + = Nom * Min(a*rate+b,strike) * accrualperiod = + = Nom * [a*rate+b + Min(0,strike-a*rate-b)] * accrualperiod = + = Nom * a*rate+b * accrualperiod + Nom * Min(strike-b-a*rate,0) * accrualperiod + --> If a>0 (assuming positive effective strike): + Payoff = VanillaFloatingLeg - Call(a*rate+b,strike) + --> If a<0 (assuming positive effective strike): + Payoff = VanillaFloatingLeg + Nom * Min(strike-b+|a|*rate+,0) * accrualperiod = + = VanillaFloatingLeg + Put(|a|*rate+b,strike) + */ + + // Positive gearing + List cappedLeg_p = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps,floors0, + vars.volatility,gearing_p,spread_p); + Swap capLeg_p = new Swap(fixedLeg,cappedLeg_p); + capLeg_p.setPricingEngine(engine); + Cap cap_p = new Cap(floatLeg_p, new InitializedList(1,capstrike)); + cap_p.setPricingEngine(vars.makeEngine(vars.volatility)); + npvVanilla = vanillaLeg_p.NPV(); + npvCappedLeg = capLeg_p.NPV(); + npvCap = cap_p.NPV(); + error = Math.Abs(npvCappedLeg - (npvVanilla-npvCap)); + if (error>tolerance) + { + Assert.Fail("\nCapped Leg: gearing=" + gearing_p + ", " + + "spread= " + spread_p *100 + + "%, strike=" + capstrike*100 + "%, " + + "effective strike= " + (capstrike-spread_p)/gearing_p*100 + + "%\n" + + " Capped Floating Leg NPV: " + npvCappedLeg + "\n" + + " Vanilla Leg NPV: " + npvVanilla + "\n" + + " Cap NPV: " + npvCap + "\n" + + " Floating Leg NPV - Cap NPV: " + (npvVanilla - npvCap) + "\n" + + " Diff: " + error ); + } + + // Negative gearing + List cappedLeg_n = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps,floors0, + vars.volatility,gearing_n,spread_n); + Swap capLeg_n = new Swap(fixedLeg,cappedLeg_n); + capLeg_n.setPricingEngine(engine); + Floor floor_n = new Floor(floatLeg, new InitializedList(1,(capstrike-spread_n)/gearing_n)); + floor_n.setPricingEngine(vars.makeEngine(vars.volatility)); + npvVanilla = vanillaLeg_n.NPV(); + npvCappedLeg = capLeg_n.NPV(); + npvFloor = floor_n.NPV(); + error = Math.Abs(npvCappedLeg - (npvVanilla+ gearing_n*npvFloor)); + if (error>tolerance) + { + Assert.Fail("\nCapped Leg: gearing=" + gearing_n + ", " + + "spread= " + spread_n *100 + + "%, strike=" + capstrike*100 + "%, " + + "effective strike= " + (capstrike-spread_n)/gearing_n*100 + + "%\n" + + " Capped Floating Leg NPV: " + npvCappedLeg + "\n" + + " npv Vanilla: " + npvVanilla + "\n" + + " npvFloor: " + npvFloor + "\n" + + " Floating Leg NPV - Cap NPV: " + (npvVanilla + gearing_n*npvFloor) + "\n" + + " Diff: " + error ); + } + + /* gearing = a and spread = b + FLOORED coupon - Decomposition of payoff + Payoff + = Nom * Max(a*rate+b,strike) * accrualperiod = + = Nom * [a*rate+b + Max(0,strike-a*rate-b)] * accrualperiod = + = Nom * a*rate+b * accrualperiod + Nom * Max(strike-b-a*rate,0) * accrualperiod + --> If a>0 (assuming positive effective strike): + Payoff = VanillaFloatingLeg + Put(a*rate+b,strike) + --> If a<0 (assuming positive effective strike): + Payoff = VanillaFloatingLeg + Nom * Max(strike-b+|a|*rate+,0) * accrualperiod = + = VanillaFloatingLeg - Call(|a|*rate+b,strike) + */ + + // Positive gearing + List flooredLeg_p1 = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps0,floors, + vars.volatility,gearing_p,spread_p); + Swap floorLeg_p1 = new Swap(fixedLeg,flooredLeg_p1); + floorLeg_p1.setPricingEngine(engine); + Floor floor_p1 = new Floor(floatLeg_p, new InitializedList(1,floorstrike)); + floor_p1.setPricingEngine(vars.makeEngine(vars.volatility)); + npvVanilla = vanillaLeg_p.NPV(); + npvFlooredLeg = floorLeg_p1.NPV(); + npvFloor = floor_p1.NPV(); + error = Math.Abs(npvFlooredLeg - (npvVanilla+npvFloor)); + if (error>tolerance) + { + Assert.Fail("\nFloored Leg: gearing=" + gearing_p + ", " + + "spread= " + spread_p *100+ "%, strike=" + floorstrike *100 + "%, " + + "effective strike= " + (floorstrike-spread_p)/gearing_p*100 + + "%\n" + + " Floored Floating Leg NPV: " + npvFlooredLeg + + "\n" + + " Floating Leg NPV + Floor NPV: " + (npvVanilla + npvFloor) + + "\n" + + " Diff: " + error ); + } + // Negative gearing + List flooredLeg_n = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps0,floors, + vars.volatility,gearing_n,spread_n); + Swap floorLeg_n = new Swap(fixedLeg,flooredLeg_n); + floorLeg_n.setPricingEngine(engine); + Cap cap_n = new Cap(floatLeg, new InitializedList(1,(floorstrike-spread_n)/gearing_n)); + cap_n.setPricingEngine(vars.makeEngine(vars.volatility)); + npvVanilla = vanillaLeg_n.NPV(); + npvFlooredLeg = floorLeg_n.NPV(); + npvCap = cap_n.NPV(); + error = Math.Abs(npvFlooredLeg - (npvVanilla - gearing_n*npvCap)); + if (error>tolerance) + { + Assert.Fail("\nCapped Leg: gearing=" + gearing_n + ", " + + "spread= " + spread_n *100 + + "%, strike=" + floorstrike*100 + "%, " + + "effective strike= " + (floorstrike-spread_n)/gearing_n*100 + + "%\n" + + " Capped Floating Leg NPV: " + npvFlooredLeg + "\n" + + " Floating Leg NPV - Cap NPV: " + (npvVanilla - gearing_n*npvCap) + "\n" + + " Diff: " + error ); + } + /* gearing = a and spread = b + COLLARED coupon - Decomposition of payoff + Payoff = Nom * Min(caprate,Max(a*rate+b,floorrate)) * accrualperiod + --> If a>0 (assuming positive effective strike): + Payoff = VanillaFloatingLeg - Collar(a*rate+b, floorrate, caprate) + --> If a<0 (assuming positive effective strike): + Payoff = VanillaFloatingLeg + Collar(|a|*rate+b, caprate, floorrate) + */ + // Positive gearing + List collaredLeg_p = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps,floors, + vars.volatility,gearing_p,spread_p); + Swap collarLeg_p1 = new Swap(fixedLeg,collaredLeg_p); + collarLeg_p1.setPricingEngine(engine); + Collar collar_p = new Collar(floatLeg_p,new InitializedList(1,capstrike), + new InitializedList(1,floorstrike)); + collar_p.setPricingEngine(vars.makeEngine(vars.volatility)); + npvVanilla = vanillaLeg_p.NPV(); + npvCollaredLeg = collarLeg_p1.NPV(); + npvCollar = collar_p.NPV(); + error = Math.Abs(npvCollaredLeg - (npvVanilla - npvCollar)); + if (error>tolerance) + { + Assert.Fail("\nCollared Leg: gearing=" + gearing_p + ", " + + "spread= " + spread_p*100 + "%, strike=" + + floorstrike*100 + "% and " + capstrike*100 + + "%, " + + "effective strike=" + (floorstrike-spread_p)/gearing_p*100 + + "% and " + (capstrike-spread_p)/gearing_p*100 + + "%\n" + + " Collared Floating Leg NPV: " + npvCollaredLeg + + "\n" + + " Floating Leg NPV - Collar NPV: " + (npvVanilla - npvCollar) + + "\n" + + " Diff: " + error ); + } + // Negative gearing + List collaredLeg_n = vars.makeCapFlooredLeg(vars.startDate,vars.length,caps,floors, + vars.volatility,gearing_n,spread_n); + Swap collarLeg_n1 = new Swap(fixedLeg,collaredLeg_n); + collarLeg_n1.setPricingEngine(engine); + Collar collar_n = new Collar(floatLeg,new InitializedList(1,(floorstrike-spread_n)/gearing_n), + new InitializedList(1,(capstrike-spread_n)/gearing_n)); + collar_n.setPricingEngine(vars.makeEngine(vars.volatility)); + npvVanilla = vanillaLeg_n.NPV(); + npvCollaredLeg = collarLeg_n1.NPV(); + npvCollar = collar_n.NPV(); + error = Math.Abs(npvCollaredLeg - (npvVanilla - gearing_n*npvCollar)); + if (error>tolerance) + { + Assert.Fail("\nCollared Leg: gearing=" + gearing_n + ", " + + "spread= " + spread_n*100 + "%, strike=" + + floorstrike*100 + "% and " + capstrike*100 + + "%, " + + "effective strike=" + (floorstrike-spread_n)/gearing_n*100 + + "% and " + (capstrike-spread_n)/gearing_n*100 + + "%\n" + + " Collared Floating Leg NPV: " + npvCollaredLeg + + "\n" + + " Floating Leg NPV - Collar NPV: " + (npvVanilla - gearing_n*npvCollar) + + "\n" + + " Diff: " + error ); + } + } + } +} diff --git a/Test/T_CliquetOption.cs b/Test/T_CliquetOption.cs new file mode 100644 index 000000000..577555c50 --- /dev/null +++ b/Test/T_CliquetOption.cs @@ -0,0 +1,295 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; +using System; +using System.Collections.Generic; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_CliquetOption + { + private void REPORT_FAILURE( string greekName, + StrikedTypePayoff payoff, + Exercise exercise, + double s, + double q, + double r, + Date today, + double v, + double expected, + double calculated, + double error, + double tolerance ) + { + QAssert.Fail( payoff.optionType() + " option :\n" + + " spot value: " + s + "\n" + + " moneyness: " + payoff.strike() + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testValues() + { + // Testing Cliquet option values + + Date today = Date.Today; + DayCounter dc = new Actual360(); + + SimpleQuote spot = new SimpleQuote(60.0); + SimpleQuote qRate = new SimpleQuote(0.04); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.08); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.30); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + BlackScholesMertonProcess process = new BlackScholesMertonProcess( + new Handle(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + IPricingEngine engine = new AnalyticCliquetEngine(process); + + List reset = new List(); + reset.Add(today + 90); + Date maturity = today + 360; + Option.Type type = Option.Type.Call; + double moneyness = 1.1; + + PercentageStrikePayoff payoff = new PercentageStrikePayoff(type, moneyness); + EuropeanExercise exercise = new EuropeanExercise(maturity); + + CliquetOption option = new CliquetOption(payoff, exercise, reset); + option.setPricingEngine(engine); + + double calculated = option.NPV(); + double expected = 4.4064; // Haug, p.37 + double error = Math.Abs(calculated-expected); + double tolerance = 1e-4; + if (error > tolerance) + { + Assert.Fail("value", payoff, exercise, spot.value(), + qRate.value(), rRate.value(), today, + vol.value(), expected, calculated, + error, tolerance); + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testGreeks() + { + // Testing Cliquet option greek + testOptionGreeks( process => new AnalyticCliquetEngine( process ) ); + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testPerformanceGreeks() + { + // Testing Performance option greek + testOptionGreeks( process => new AnalyticPerformanceEngine( process ) ); + } + + private void testOptionGreeks(ForwardVanillaEngine.GetOriginalEngine getEngine) + { + + SavedSettings backup = new SavedSettings(); + + Dictionary calculated = new Dictionary(), + expected = new Dictionary(), + tolerance = new Dictionary(); + tolerance["delta"] = 1.0e-5; + tolerance["gamma"] = 1.0e-5; + tolerance["theta"] = 1.0e-5; + tolerance["rho"] = 1.0e-5; + tolerance["divRho"] = 1.0e-5; + tolerance["vega"] = 1.0e-5; + + Option.Type[] types = { Option.Type.Call, Option.Type.Put }; + double[] moneyness = { 0.9, 1.0, 1.1 }; + double[] underlyings = { 100.0 }; + double[] qRates = { 0.04, 0.05, 0.06 }; + double[] rRates = { 0.01, 0.05, 0.15 }; + int[] lengths = { 1, 2 }; + Frequency[] frequencies = { Frequency.Semiannual, Frequency.Quarterly, }; + double[] vols = { 0.11, 0.50, 1.20 }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + Settings.setEvaluationDate(today); + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + Handle qTS = new Handle(Utilities.flatRate(qRate, dc)); + SimpleQuote rRate = new SimpleQuote(0.0); + Handle rTS = new Handle(Utilities.flatRate(rRate, dc)); + SimpleQuote vol = new SimpleQuote(0.0); + Handle volTS = new Handle(Utilities.flatVol(vol, dc)); + + BlackScholesMertonProcess process = new BlackScholesMertonProcess(new Handle(spot),qTS, rTS, volTS); + + for (int i=0; i reset = new List(); + for (Date d = today + new Period(frequencies[kk]); + d < maturity.lastDate(); + d += new Period(frequencies[kk])) + reset.Add(d); + + IPricingEngine engine = getEngine( process ); + + CliquetOption option = new CliquetOption(payoff, maturity, reset); + option.setPricingEngine(engine); + + for (int l=0; l spot.value()*1.0e-5) + { + // perturb spot and get delta and gamma + double du = u*1.0e-4; + spot.setValue(u+du); + double value_p = option.NPV(), + delta_p = option.delta(); + spot.setValue(u-du); + double value_m = option.NPV(), + delta_m = option.delta(); + spot.setValue(u); + expected["delta"] = (value_p - value_m)/(2*du); + expected["gamma"] = (delta_p - delta_m)/(2*du); + + // perturb rates and get rho and dividend rho + double dr = r*1.0e-4; + rRate.setValue(r+dr); + value_p = option.NPV(); + rRate.setValue(r-dr); + value_m = option.NPV(); + rRate.setValue(r); + expected["rho"] = (value_p - value_m)/(2*dr); + + double dq = q*1.0e-4; + qRate.setValue(q+dq); + value_p = option.NPV(); + qRate.setValue(q-dq); + value_m = option.NPV(); + qRate.setValue(q); + expected["divRho"] = (value_p - value_m)/(2*dq); + + // perturb volatility and get vega + double dv = v*1.0e-4; + vol.setValue(v+dv); + value_p = option.NPV(); + vol.setValue(v-dv); + value_m = option.NPV(); + vol.setValue(v); + expected["vega"] = (value_p - value_m)/(2*dv); + + // perturb date and get theta + double dT = dc.yearFraction(today-1, today+1); + Settings.setEvaluationDate(today-1); + value_m = option.NPV(); + Settings.setEvaluationDate(today+1); + value_p = option.NPV(); + Settings.setEvaluationDate(today); + expected["theta"] = (value_p - value_m)/dT; + + // compare + foreach (var it in calculated) + { + String greek = it.Key; + double expct = expected [greek], + calcl = calculated[greek], + tol = tolerance [greek]; + double error = Utilities.relativeError(expct,calcl,u); + if (error>tol) + { + REPORT_FAILURE(greek, payoff, maturity, + u, q, r, today, v, + expct, calcl, error, tol); + } + } + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Test/T_Cms.cs b/Test/T_Cms.cs new file mode 100644 index 000000000..52927c479 --- /dev/null +++ b/Test/T_Cms.cs @@ -0,0 +1,467 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_Cms + { + private class CommonVars + { + // global data + public RelinkableHandle termStructure; + + public IborIndex iborIndex; + + public Handle atmVol; + public Handle SabrVolCube1; + public Handle SabrVolCube2; + + public List yieldCurveModels; + public List numericalPricers; + public List analyticPricers; + + // cleanup + public SavedSettings backup; + + // setup + public CommonVars() + { + backup = new SavedSettings(); + + Calendar calendar = new TARGET(); + + Date referenceDate = calendar.adjust( Date.Today ); + Settings.setEvaluationDate( referenceDate ); + + termStructure = new RelinkableHandle(); + termStructure.linkTo( Utilities.flatRate( referenceDate, 0.05, new Actual365Fixed() ) ); + + // ATM Volatility structure + List atmOptionTenors = new List(); + atmOptionTenors.Add( new Period( 1, TimeUnit.Months ) ); + atmOptionTenors.Add( new Period( 6, TimeUnit.Months ) ); + atmOptionTenors.Add( new Period( 1, TimeUnit.Years ) ); + atmOptionTenors.Add( new Period( 5, TimeUnit.Years ) ); + atmOptionTenors.Add( new Period( 10, TimeUnit.Years ) ); + atmOptionTenors.Add( new Period( 30, TimeUnit.Years ) ); + + List atmSwapTenors = new List(); + atmSwapTenors.Add( new Period( 1, TimeUnit.Years ) ); + atmSwapTenors.Add( new Period( 5, TimeUnit.Years ) ); + atmSwapTenors.Add( new Period( 10, TimeUnit.Years ) ); + atmSwapTenors.Add( new Period( 30, TimeUnit.Years ) ); + + Matrix m = new Matrix( atmOptionTenors.Count, atmSwapTenors.Count ); + m[0, 0] = 0.1300; m[0, 1] = 0.1560; m[0, 2] = 0.1390; m[0, 3] = 0.1220; + m[1, 0] = 0.1440; m[1, 1] = 0.1580; m[1, 2] = 0.1460; m[1, 3] = 0.1260; + m[2, 0] = 0.1600; m[2, 1] = 0.1590; m[2, 2] = 0.1470; m[2, 3] = 0.1290; + m[3, 0] = 0.1640; m[3, 1] = 0.1470; m[3, 2] = 0.1370; m[3, 3] = 0.1220; + m[4, 0] = 0.1400; m[4, 1] = 0.1300; m[4, 2] = 0.1250; m[4, 3] = 0.1100; + m[5, 0] = 0.1130; m[5, 1] = 0.1090; m[5, 2] = 0.1070; m[5, 3] = 0.0930; + + atmVol = new Handle( + new SwaptionVolatilityMatrix( calendar, BusinessDayConvention.Following, atmOptionTenors, + atmSwapTenors, m, new Actual365Fixed() ) ); + + // Vol cubes + List optionTenors = new List(); + optionTenors.Add( new Period( 1, TimeUnit.Years ) ); + optionTenors.Add( new Period( 10, TimeUnit.Years ) ); + optionTenors.Add( new Period( 30, TimeUnit.Years ) ); + + List swapTenors = new List(); + swapTenors.Add( new Period( 2, TimeUnit.Years ) ); + swapTenors.Add( new Period( 10, TimeUnit.Years ) ); + swapTenors.Add( new Period( 30, TimeUnit.Years ) ); + + List strikeSpreads = new List(); + strikeSpreads.Add( -0.020 ); + strikeSpreads.Add( -0.005 ); + strikeSpreads.Add( +0.000 ); + strikeSpreads.Add( +0.005 ); + strikeSpreads.Add( +0.020 ); + + int nRows = optionTenors.Count * swapTenors.Count; + int nCols = strikeSpreads.Count; + Matrix volSpreadsMatrix = new Matrix( nRows, nCols ); + volSpreadsMatrix[0, 0] = 0.0599; + volSpreadsMatrix[0, 1] = 0.0049; + volSpreadsMatrix[0, 2] = 0.0000; + volSpreadsMatrix[0, 3] = -0.0001; + volSpreadsMatrix[0, 4] = 0.0127; + + volSpreadsMatrix[1, 0] = 0.0729; + volSpreadsMatrix[1, 1] = 0.0086; + volSpreadsMatrix[1, 2] = 0.0000; + volSpreadsMatrix[1, 3] = -0.0024; + volSpreadsMatrix[1, 4] = 0.0098; + + volSpreadsMatrix[2, 0] = 0.0738; + volSpreadsMatrix[2, 1] = 0.0102; + volSpreadsMatrix[2, 2] = 0.0000; + volSpreadsMatrix[2, 3] = -0.0039; + volSpreadsMatrix[2, 4] = 0.0065; + + volSpreadsMatrix[3, 0] = 0.0465; + volSpreadsMatrix[3, 1] = 0.0063; + volSpreadsMatrix[3, 2] = 0.0000; + volSpreadsMatrix[3, 3] = -0.0032; + volSpreadsMatrix[3, 4] = -0.0010; + + volSpreadsMatrix[4, 0] = 0.0558; + volSpreadsMatrix[4, 1] = 0.0084; + volSpreadsMatrix[4, 2] = 0.0000; + volSpreadsMatrix[4, 3] = -0.0050; + volSpreadsMatrix[4, 4] = -0.0057; + + volSpreadsMatrix[5, 0] = 0.0576; + volSpreadsMatrix[5, 1] = 0.0083; + volSpreadsMatrix[5, 2] = 0.0000; + volSpreadsMatrix[5, 3] = -0.0043; + volSpreadsMatrix[5, 4] = -0.0014; + + volSpreadsMatrix[6, 0] = 0.0437; + volSpreadsMatrix[6, 1] = 0.0059; + volSpreadsMatrix[6, 2] = 0.0000; + volSpreadsMatrix[6, 3] = -0.0030; + volSpreadsMatrix[6, 4] = -0.0006; + + volSpreadsMatrix[7, 0] = 0.0533; + volSpreadsMatrix[7, 1] = 0.0078; + volSpreadsMatrix[7, 2] = 0.0000; + volSpreadsMatrix[7, 3] = -0.0045; + volSpreadsMatrix[7, 4] = -0.0046; + + volSpreadsMatrix[8, 0] = 0.0545; + volSpreadsMatrix[8, 1] = 0.0079; + volSpreadsMatrix[8, 2] = 0.0000; + volSpreadsMatrix[8, 3] = -0.0042; + volSpreadsMatrix[8, 4] = -0.0020; + + List>> volSpreads = new InitializedList>>( nRows ); + for ( int i = 0; i < nRows; ++i ) + { + volSpreads[i] = new InitializedList>( nCols ); + for ( int j = 0; j < nCols; ++j ) + { + volSpreads[i][j] = new Handle( new SimpleQuote( volSpreadsMatrix[i, j] ) ); + } + } + + iborIndex = new Euribor6M( termStructure ); + SwapIndex swapIndexBase = new EuriborSwapIsdaFixA( new Period( 10, TimeUnit.Years ), termStructure ); + SwapIndex shortSwapIndexBase = new EuriborSwapIsdaFixA( new Period( 2, TimeUnit.Years ), termStructure ); + + bool vegaWeightedSmileFit = false; + + SabrVolCube2 = new Handle( + new SwaptionVolCube2( atmVol, + optionTenors, + swapTenors, + strikeSpreads, + volSpreads, + swapIndexBase, + shortSwapIndexBase, + vegaWeightedSmileFit ) ); + SabrVolCube2.link.enableExtrapolation(); + + List>> guess = new InitializedList>>( nRows ); + for ( int i = 0; i < nRows; ++i ) + { + guess[i] = new InitializedList>( 4 ); + guess[i][0] = new Handle( new SimpleQuote( 0.2 ) ); + guess[i][1] = new Handle( new SimpleQuote( 0.5 ) ); + guess[i][2] = new Handle( new SimpleQuote( 0.4 ) ); + guess[i][3] = new Handle( new SimpleQuote( 0.0 ) ); + } + List isParameterFixed = new InitializedList( 4, false ); + isParameterFixed[1] = true; + + // FIXME + bool isAtmCalibrated = false; + + SabrVolCube1 = new Handle( + new SwaptionVolCube1x( atmVol, + optionTenors, + swapTenors, + strikeSpreads, + volSpreads, + swapIndexBase, + shortSwapIndexBase, + vegaWeightedSmileFit, + guess, + isParameterFixed, + isAtmCalibrated ) ); + SabrVolCube1.link.enableExtrapolation(); + + yieldCurveModels = new List(); + yieldCurveModels.Add( GFunctionFactory.YieldCurveModel.Standard ); + yieldCurveModels.Add( GFunctionFactory.YieldCurveModel.ExactYield ); + yieldCurveModels.Add( GFunctionFactory.YieldCurveModel.ParallelShifts ); + yieldCurveModels.Add( GFunctionFactory.YieldCurveModel.NonParallelShifts ); + yieldCurveModels.Add( GFunctionFactory.YieldCurveModel.NonParallelShifts ); // for linear tsr model + + Handle zeroMeanRev = new Handle( new SimpleQuote( 0.0 ) ); + + numericalPricers = new List(); + analyticPricers = new List(); + for ( int j = 0; j < yieldCurveModels.Count; ++j ) + { + if ( j < yieldCurveModels.Count - 1 ) + numericalPricers.Add( new NumericHaganPricer( atmVol, yieldCurveModels[j], zeroMeanRev ) ); + else + numericalPricers.Add( new LinearTsrPricer( atmVol, zeroMeanRev ) ); + + analyticPricers.Add( new AnalyticHaganPricer( atmVol, yieldCurveModels[j], zeroMeanRev ) ); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testFairRate() + { + // Testing Hagan-pricer flat-vol equivalence for coupons + CommonVars vars = new CommonVars(); + + SwapIndex swapIndex = new SwapIndex("EuriborSwapIsdaFixA", + new Period(10,TimeUnit.Years), + vars.iborIndex.fixingDays(), + vars.iborIndex.currency(), + vars.iborIndex.fixingCalendar(), + new Period(1,TimeUnit.Years), + BusinessDayConvention.Unadjusted, + vars.iborIndex.dayCounter(),//?? + vars.iborIndex); + + // FIXME + //shared_ptr swapIndex(new + // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure())); + Date startDate = vars.termStructure.link.referenceDate() + new Period(20,TimeUnit.Years); + Date paymentDate = startDate + new Period(1,TimeUnit.Years); + Date endDate = paymentDate; + double nominal = 1.0; + double? infiniteCap = null; + double? infiniteFloor = null; + double gearing = 1.0; + double spread = 0.0; + CappedFlooredCmsCoupon coupon = new CappedFlooredCmsCoupon(nominal ,paymentDate, + startDate, endDate, + swapIndex.fixingDays(), swapIndex, + gearing, spread, + infiniteCap, infiniteFloor, + startDate, endDate, + vars.iborIndex.dayCounter()); + + for (int j=0; j tol) + Assert.Fail("\nCoupon payment date: " + paymentDate + + "\nCoupon start date: " + startDate + + "\nCoupon floor: " + (infiniteFloor) + + "\nCoupon gearing: " + (gearing) + + "\nCoupon swap index: " + swapIndex.name() + + "\nCoupon spread: " + (spread) + + "\nCoupon cap: " + (infiniteCap) + + "\nCoupon DayCounter: " + vars.iborIndex.dayCounter()+ + "\nYieldCurve Model: " + vars.yieldCurveModels[j] + + "\nNumerical Pricer: " + (rate0) + (linearTsr ? " (Linear TSR Model)" : "") + + "\nAnalytic Pricer: " + (rate1) + + "\ndifference: " + (difference) + + "\ntolerance: " + (tol)); + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testCmsSwap() + { + // Testing Hagan-pricer flat-vol equivalence for swaps + CommonVars vars = new CommonVars(); + + SwapIndex swapIndex = new SwapIndex("EuriborSwapIsdaFixA", + new Period(10,TimeUnit.Years), + vars.iborIndex.fixingDays(), + vars.iborIndex.currency(), + vars.iborIndex.fixingCalendar(), + new Period(1,TimeUnit.Years), + BusinessDayConvention.Unadjusted, + vars.iborIndex.dayCounter(),//?? + vars.iborIndex); + // FIXME + //shared_ptr swapIndex(new + // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure())); + double spread = 0.0; + List swapLengths = new List(); + swapLengths.Add(1); + swapLengths.Add(5); + swapLengths.Add(6); + swapLengths.Add(10); + int n = swapLengths.Count; + List cms = new List(n); + for (int i=0; i tol) + Assert.Fail("\nLength in Years: " + swapLengths[sl] + + "\nswap index: " + swapIndex.name() + + "\nibor index: " + vars.iborIndex.name() + + "\nspread: " + (spread) + + "\nYieldCurve Model: " + vars.yieldCurveModels[j] + + "\nNumerical Pricer: " + (priceNum) + (linearTsr ? " (Linear TSR Model)" : "") + + "\nAnalytic Pricer: " + (priceAn) + + "\ndifference: " + (difference) + + "\ntolerance: " + (tol)); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testParity() + { + // Testing put-call parity for capped-floored CMS coupons + + CommonVars vars = new CommonVars(); + + List > swaptionVols = new List>(); + swaptionVols.Add(vars.atmVol); + swaptionVols.Add(vars.SabrVolCube1); + swaptionVols.Add(vars.SabrVolCube2); + + SwapIndex swapIndex = new EuriborSwapIsdaFixA(new Period(10,TimeUnit.Years), + vars.iborIndex.forwardingTermStructure()); + Date startDate = vars.termStructure.link.referenceDate() + new Period(20,TimeUnit.Years); + Date paymentDate = startDate + new Period(1,TimeUnit.Years); + Date endDate = paymentDate; + double nominal = 1.0; + double? infiniteCap = null; + double? infiniteFloor = null; + double gearing = 1.0; + double spread = 0.0; + double discount = vars.termStructure.link.discount(paymentDate); + CappedFlooredCmsCoupon swaplet = new CappedFlooredCmsCoupon(nominal , paymentDate, + startDate, endDate,swapIndex.fixingDays(),swapIndex,gearing, spread,infiniteCap, infiniteFloor, + startDate, endDate,vars.iborIndex.dayCounter()); + for (double strike = .02; strike<.12; strike+=0.05) + { + CappedFlooredCmsCoupon caplet = new CappedFlooredCmsCoupon(nominal ,paymentDate, + startDate, endDate,swapIndex.fixingDays(),swapIndex,gearing, spread,strike, infiniteFloor, + startDate, endDate,vars.iborIndex.dayCounter()); + CappedFlooredCmsCoupon floorlet = new CappedFlooredCmsCoupon(nominal , paymentDate, + startDate, endDate,swapIndex.fixingDays(),swapIndex,gearing, spread,infiniteCap, strike, + startDate, endDate,vars.iborIndex.dayCounter()); + + for (int i=0; i pricers = new List(2); + pricers.Add(vars.numericalPricers[j]); + pricers.Add(vars.analyticPricers[j]); + for (int k=0; k tol) + Assert.Fail("\nCoupon payment date: " + paymentDate + + "\nCoupon start date: " + startDate + + "\nCoupon gearing: " + (gearing) + + "\nCoupon swap index: " + swapIndex.name() + + "\nCoupon spread: " + (spread) + + "\nstrike: " + (strike) + + "\nCoupon DayCounter: " + vars.iborIndex.dayCounter() + + "\nYieldCurve Model: " + vars.yieldCurveModels[j] + + (k==0 ? "\nNumerical Pricer" : "\nAnalytic Pricer") + + (linearTsr ? " (Linear TSR Model)" : "") + + "\nSwaplet price: " + (swapletPrice) + + "\nCaplet price: " + (capletPrice) + + "\nFloorlet price: " + (floorletPrice) + + "\ndifference: " + difference + + "\ntolerance: " + (tol)); + + } + } + + } + + } + } + } +} diff --git a/Test/T_DigitalCoupon.cs b/Test/T_DigitalCoupon.cs new file mode 100644 index 000000000..95d508324 --- /dev/null +++ b/Test/T_DigitalCoupon.cs @@ -0,0 +1,1070 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_DigitalCoupon + { + private class CommonVars + { + // global data + public Date today, settlement; + public double nominal; + public Calendar calendar; + public IborIndex index; + public int fixingDays; + public RelinkableHandle termStructure; + public double optionTolerance; + public double blackTolerance; + + // cleanup + SavedSettings backup; + + // setup + public CommonVars() + { + backup = new SavedSettings(); + termStructure = new RelinkableHandle(); + fixingDays = 2; + nominal = 1000000.0; + index = new Euribor6M(termStructure); + calendar = index.fixingCalendar(); + today = calendar.adjust(Settings.evaluationDate()); + Settings.setEvaluationDate(today); + settlement = calendar.advance(today,fixingDays,TimeUnit.Days); + termStructure.linkTo(Utilities.flatRate(settlement,0.05,new Actual365Fixed())); + optionTolerance = 1.0e-04; + blackTolerance = 1e-10; + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testAssetOrNothing() + { + + // Testing European asset-or-nothing digital coupon + + /* Call Payoff = (aL+b)Heaviside(aL+b-X) = a Max[L-X'] + (b+aX')Heaviside(L-X') + Value Call = aF N(d1') + bN(d2') + Put Payoff = (aL+b)Heaviside(X-aL-b) = -a Max[X-L'] + (b+aX')Heaviside(X'-L) + Value Put = aF N(-d1') + bN(-d2') + where: + d1' = ln(F/X')/stdDev + 0.5*stdDev; + */ + + CommonVars vars = new CommonVars(); + + double[] vols = { 0.05, 0.15, 0.30 }; + double[] strikes = { 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 }; + double[] gearings = { 1.0, 2.8 }; + double[] spreads = { 0.0, 0.005 }; + + double gap = 1e-7; /* low, in order to compare digital option value + with black formula result */ + DigitalReplication replication = new DigitalReplication(Replication.Type.Central, gap); + for (int i = 0; i< vols.Length; i++) + { + double capletVol = vols[i]; + RelinkableHandle vol = new RelinkableHandle(); + vol.linkTo(new ConstantOptionletVolatility(vars.today,vars.calendar, BusinessDayConvention.Following, + capletVol, new Actual360())); + for (int j=0; jvars.optionTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nOption price by replication = " + optionPrice + + "\nOption price by Cox-Rubinstein formula = " + nd1Price + + "\nError " + error); + + // Check digital option price vs N(d1) price using Vanilla Option class + if (spread==0.0) + { + Exercise exercise = new EuropeanExercise(exerciseDate); + double discountAtFixing = vars.termStructure.link.discount(exerciseDate); + SimpleQuote fwd = new SimpleQuote(effFwd*discountAtFixing); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(vars.today, qRate, new Actual360()); + SimpleQuote vol1 = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(vars.today, capletVol, new Actual360()); + StrikedTypePayoff callPayoff = new AssetOrNothingPayoff(Option.Type.Call,effStrike); + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess( + new Handle(fwd), + new Handle(qTS), + new Handle(vars.termStructure), + new Handle(volTS)); + IPricingEngine engine = new AnalyticEuropeanEngine(stochProcess); + VanillaOption callOpt = new VanillaOption(callPayoff, exercise); + callOpt.setPricingEngine(engine); + double callVO = vars.nominal * gearing + * accrualPeriod * callOpt.NPV() + * discount / discountAtFixing + * forward / effFwd; + error = Math.Abs(nd1Price - callVO); + if (error>vars.blackTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nOption price by Black asset-ot-nothing payoff = " + callVO + + "\nOption price by Cox-Rubinstein = " + nd1Price + + "\nError " + error ); + } + + // Floating Rate Coupon + Put Digital option + DigitalCoupon digitalFlooredCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, + false, nullstrike,strike, Position.Type.Long, false, nullstrike,replication); + digitalFlooredCoupon.setPricer(pricer); + + // Check digital option price vs N(d1) price + N_d1 = phi.value(-d1); + N_d2 = phi.value(-d2); + nd1Price = (gearing * effFwd * N_d1 + spread * N_d2) + * vars.nominal * accrualPeriod * discount; + optionPrice = digitalFlooredCoupon.putOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(nd1Price - optionPrice); + if (error>vars.optionTolerance) + Assert.Fail("\nDigital Put Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nOption price by replication = " + optionPrice + + "\nOption price by Cox-Rubinstein = " + nd1Price + + "\nError " + error ); + + // Check digital option price vs N(d1) price using Vanilla Option class + if (spread==0.0) + { + Exercise exercise = new EuropeanExercise(exerciseDate); + double discountAtFixing = vars.termStructure.link.discount(exerciseDate); + SimpleQuote fwd = new SimpleQuote(effFwd*discountAtFixing); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(vars.today, qRate, new Actual360()); + //SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(vars.today, capletVol, new Actual360()); + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess( + new Handle(fwd), + new Handle(qTS), + new Handle(vars.termStructure), + new Handle(volTS)); + StrikedTypePayoff putPayoff = new AssetOrNothingPayoff(Option.Type.Put, effStrike); + IPricingEngine engine = new AnalyticEuropeanEngine(stochProcess); + VanillaOption putOpt = new VanillaOption(putPayoff, exercise); + putOpt.setPricingEngine(engine); + double putVO = vars.nominal * gearing + * accrualPeriod * putOpt.NPV() + * discount / discountAtFixing + * forward / effFwd; + error = Math.Abs(nd1Price - putVO); + if (error>vars.blackTolerance) + Assert.Fail("\nDigital Put Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nOption price by Black asset-ot-nothing payoff = " + putVO + + "\nOption price by Cox-Rubinstein = " + nd1Price + + "\nError " + error ); + } + } + } + } + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testAssetOrNothingDeepInTheMoney() + { + // Testing European deep in-the-money asset-or-nothing digital coupon + CommonVars vars = new CommonVars(); + + double gearing = 1.0; + double spread = 0.0; + + double capletVolatility = 0.0001; + RelinkableHandle volatility = new RelinkableHandle(); + volatility.linkTo(new ConstantOptionletVolatility(vars.today, vars.calendar, BusinessDayConvention.Following, + capletVolatility, new Actual360())); + double gap = 1e-4; + DigitalReplication replication = new DigitalReplication(Replication.Type.Central, gap); + + for (int k = 0; k<10; k++) + { + // Loop on start and end dates + Date startDate = vars.calendar.advance(vars.settlement,new Period(k+1,TimeUnit.Years)); + Date endDate = vars.calendar.advance(vars.settlement,new Period(k+2,TimeUnit.Years)); + double? nullstrike = null; + Date paymentDate = endDate; + + FloatingRateCoupon underlying = new IborCoupon(paymentDate, vars.nominal,startDate, endDate, + vars.fixingDays, vars.index, gearing, spread); + + // Floating Rate Coupon - Deep-in-the-money Call Digital option + double strike = 0.001; + DigitalCoupon digitalCappedCoupon = new DigitalCoupon(underlying,strike, Position.Type.Short, false, + nullstrike,nullstrike, Position.Type.Short, false, nullstrike,replication); + IborCouponPricer pricer = new BlackIborCouponPricer(volatility); + digitalCappedCoupon.setPricer(pricer); + + // Check price vs its target price + double accrualPeriod = underlying.accrualPeriod(); + double discount = vars.termStructure.link.discount(endDate); + + double targetOptionPrice = underlying.price(vars.termStructure); + double targetPrice = 0.0; + double digitalPrice = digitalCappedCoupon.price(vars.termStructure); + double error = Math.Abs(targetPrice - digitalPrice); + double tolerance = 1e-08; + if (error>tolerance) + Assert.Fail("\nFloating Coupon - Digital Call Option:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nCoupon Price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError = " + error ); + + // Check digital option price + double replicationOptionPrice = digitalCappedCoupon.callOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + double optionTolerance = 1e-08; + if (error>optionTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + replicationOptionPrice + + "\nTarget price = " + targetOptionPrice + + "\nError = " + error); + + // Floating Rate Coupon + Deep-in-the-money Put Digital option + strike = 0.99; + DigitalCoupon digitalFlooredCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, false, + nullstrike,strike, Position.Type.Long, false, nullstrike,replication); + digitalFlooredCoupon.setPricer(pricer); + + // Check price vs its target price + targetOptionPrice = underlying.price(vars.termStructure); + targetPrice = underlying.price(vars.termStructure) + targetOptionPrice ; + digitalPrice = digitalFlooredCoupon.price(vars.termStructure); + error = Math.Abs(targetPrice - digitalPrice); + tolerance = 2.5e-06; + if (error>tolerance) + Assert.Fail("\nFloating Coupon + Digital Put Option:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nDigital coupon price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError " + error); + + // Check digital option + replicationOptionPrice = digitalFlooredCoupon.putOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + optionTolerance = 2.5e-06; + if (error>optionTolerance) + Assert.Fail("\nDigital Put Option:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + replicationOptionPrice + + "\nTarget price = " + targetOptionPrice + + "\nError " + error); + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testAssetOrNothingDeepOutTheMoney() + { + // Testing European deep out-the-money asset-or-nothing digital coupon + CommonVars vars = new CommonVars(); + + double gearing = 1.0; + double spread = 0.0; + + double capletVolatility = 0.0001; + RelinkableHandle volatility = new RelinkableHandle(); + volatility.linkTo(new ConstantOptionletVolatility(vars.today, vars.calendar, BusinessDayConvention.Following, + capletVolatility, new Actual360())); + double gap = 1e-4; + DigitalReplication replication = new DigitalReplication(Replication.Type.Central, gap); + + for (int k = 0; k<10; k++) + { + // loop on start and end dates + Date startDate = vars.calendar.advance(vars.settlement,new Period(k+1,TimeUnit.Years)); + Date endDate = vars.calendar.advance(vars.settlement,new Period(k+2,TimeUnit.Years)); + double? nullstrike = null; + Date paymentDate = endDate; + + FloatingRateCoupon underlying = new IborCoupon(paymentDate, vars.nominal,startDate, endDate, + vars.fixingDays, vars.index, gearing, spread); + + // Floating Rate Coupon - Deep-out-of-the-money Call Digital option + double strike = 0.99; + DigitalCoupon digitalCappedCoupon = new DigitalCoupon(underlying,strike, Position.Type.Short, false, + nullstrike,nullstrike, Position.Type.Long, false, nullstrike,replication/*Replication::Central, gap*/); + IborCouponPricer pricer = new BlackIborCouponPricer(volatility); + digitalCappedCoupon.setPricer(pricer); + + // Check price vs its target + double accrualPeriod = underlying.accrualPeriod(); + double discount = vars.termStructure.link.discount(endDate); + + double targetPrice = underlying.price(vars.termStructure); + double digitalPrice = digitalCappedCoupon.price(vars.termStructure); + double error = Math.Abs(targetPrice - digitalPrice); + double tolerance = 1e-10; + if (error>tolerance) + Assert.Fail("\nFloating Coupon - Digital Call Option :" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nCoupon price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError = " + error ); + + // Check digital option price + double targetOptionPrice = 0.0; + double replicationOptionPrice = digitalCappedCoupon.callOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + double optionTolerance = 1e-08; + if (error>optionTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + replicationOptionPrice + + "\nTarget price = " + targetOptionPrice + + "\nError = " + error ); + + // Floating Rate Coupon - Deep-out-of-the-money Put Digital option + strike = 0.01; + DigitalCoupon digitalFlooredCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, false, + nullstrike,strike, Position.Type.Long, false, nullstrike,replication); + digitalFlooredCoupon.setPricer(pricer); + + // Check price vs its target + targetPrice = underlying.price(vars.termStructure); + digitalPrice = digitalFlooredCoupon.price(vars.termStructure); + tolerance = 1e-08; + error = Math.Abs(targetPrice - digitalPrice); + if (error>tolerance) + Assert.Fail("\nFloating Coupon + Digital Put Coupon:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nCoupon price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError = " + error ); + + // Check digital option + targetOptionPrice = 0.0; + replicationOptionPrice = digitalFlooredCoupon.putOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + if (error>optionTolerance) + Assert.Fail("\nDigital Put Coupon:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + replicationOptionPrice + + "\nTarget price = " + targetOptionPrice + + "\nError = " + error ); + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testCashOrNothing() + { + // Testing European cash-or-nothing digital coupon + + /* Call Payoff = R Heaviside(aL+b-X) + Value Call = R N(d2') + Put Payoff = R Heaviside(X-aL-b) + Value Put = R N(-d2') + where: + d2' = ln(F/X')/stdDev - 0.5*stdDev; + */ + + CommonVars vars = new CommonVars(); + + double[] vols = { 0.05, 0.15, 0.30 }; + double[] strikes = { 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 }; + + double gearing = 3.0; + double spread = -0.0002; + + double gap = 1e-08; /* very low, in order to compare digital option value + with black formula result */ + DigitalReplication replication = new DigitalReplication(Replication.Type.Central, gap); + RelinkableHandle vol = new RelinkableHandle(); + + for (int i = 0; i< vols.Length; i++) + { + double capletVol = vols[i]; + vol.linkTo(new ConstantOptionletVolatility(vars.today,vars.calendar, BusinessDayConvention.Following, + capletVol, new Actual360())); + for (int j = 0; j< strikes.Length; j++) + { + double strike = strikes[j]; + for (int k = 0; k<10; k++) + { + Date startDate = vars.calendar.advance(vars.settlement,new Period(k+1,TimeUnit.Years)); + Date endDate = vars.calendar.advance(vars.settlement,new Period(k+2,TimeUnit.Years)); + double? nullstrike = null; + double cashRate = 0.01; + + Date paymentDate = endDate; + FloatingRateCoupon underlying = new IborCoupon(paymentDate, vars.nominal,startDate, endDate, + vars.fixingDays, vars.index,gearing, spread); + // Floating Rate Coupon - Call Digital option + DigitalCoupon digitalCappedCoupon = new DigitalCoupon(underlying,strike, Position.Type.Short, false, + cashRate,nullstrike, Position.Type.Short, false, nullstrike,replication); + IborCouponPricer pricer = new BlackIborCouponPricer(vol); + digitalCappedCoupon.setPricer(pricer); + + // Check digital option price vs N(d2) price + Date exerciseDate = underlying.fixingDate(); + double forward = underlying.rate(); + double effFwd = (forward-spread)/gearing; + double effStrike = (strike-spread)/gearing; + double accrualPeriod = underlying.accrualPeriod(); + double discount = vars.termStructure.link.discount(endDate); + double stdDev = Math.Sqrt(vol.link.blackVariance(exerciseDate, effStrike)); + double ITM = Utils.blackFormulaCashItmProbability(Option.Type.Call, effStrike,effFwd, stdDev); + double nd2Price = ITM * vars.nominal * accrualPeriod * discount * cashRate; + double optionPrice = digitalCappedCoupon.callOptionRate() * + vars.nominal * accrualPeriod * discount; + double error = Math.Abs(nd2Price - optionPrice); + if (error>vars.optionTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + optionPrice + + "\nPrice by Reiner-Rubinstein = " + nd2Price + + "\nError = " + error ); + + // Check digital option price vs N(d2) price using Vanilla Option class + Exercise exercise = new EuropeanExercise(exerciseDate); + double discountAtFixing = vars.termStructure.link.discount(exerciseDate); + SimpleQuote fwd = new SimpleQuote(effFwd*discountAtFixing); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(vars.today, qRate, new Actual360()); + //SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(vars.today, capletVol, new Actual360()); + StrikedTypePayoff callPayoff = new CashOrNothingPayoff(Option.Type.Call, effStrike, cashRate); + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess( + new Handle(fwd), + new Handle(qTS), + new Handle(vars.termStructure), + new Handle(volTS)); + IPricingEngine engine = new AnalyticEuropeanEngine(stochProcess); + VanillaOption callOpt = new VanillaOption(callPayoff, exercise); + callOpt.setPricingEngine(engine); + double callVO = vars.nominal * accrualPeriod * callOpt.NPV() + * discount / discountAtFixing; + error = Math.Abs(nd2Price - callVO); + if (error>vars.blackTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nOption price by Black asset-ot-nothing payoff = " + callVO + + "\nOption price by Reiner-Rubinstein = " + nd2Price + + "\nError " + error ); + + // Floating Rate Coupon + Put Digital option + DigitalCoupon digitalFlooredCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, false, + nullstrike,strike, Position.Type.Long, false, cashRate,replication); + digitalFlooredCoupon.setPricer(pricer); + + + // Check digital option price vs N(d2) price + ITM = Utils.blackFormulaCashItmProbability(Option.Type.Put,effStrike,effFwd,stdDev); + nd2Price = ITM * vars.nominal * accrualPeriod * discount * cashRate; + optionPrice = digitalFlooredCoupon.putOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(nd2Price - optionPrice); + if (error>vars.optionTolerance) + Assert.Fail("\nPut Digital Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + optionPrice + + "\nPrice by Reiner-Rubinstein = " + nd2Price + + "\nError = " + error ); + + // Check digital option price vs N(d2) price using Vanilla Option class + StrikedTypePayoff putPayoff = new CashOrNothingPayoff(Option.Type.Put, effStrike, cashRate); + VanillaOption putOpt = new VanillaOption(putPayoff, exercise); + putOpt.setPricingEngine(engine); + double putVO = vars.nominal * accrualPeriod * putOpt.NPV() + * discount / discountAtFixing; + error = Math.Abs(nd2Price - putVO); + if (error>vars.blackTolerance) + Assert.Fail("\nDigital Put Option:" + + "\nVolatility = " + (capletVol) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nOption price by Black asset-ot-nothing payoff = " + putVO + + "\nOption price by Reiner-Rubinstein = " + nd2Price + + "\nError " + error ); + } + } + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testCashOrNothingDeepInTheMoney() + { + // Testing European deep in-the-money cash-or-nothing digital coupon + CommonVars vars = new CommonVars(); + + double gearing = 1.0; + double spread = 0.0; + + double capletVolatility = 0.0001; + RelinkableHandle volatility = new RelinkableHandle(); + volatility.linkTo(new ConstantOptionletVolatility(vars.today, vars.calendar, BusinessDayConvention.Following, + capletVolatility, new Actual360())); + + for (int k = 0; k<10; k++) + { + // Loop on start and end dates + Date startDate = vars.calendar.advance(vars.settlement,new Period(k+1,TimeUnit.Years)); + Date endDate = vars.calendar.advance(vars.settlement,new Period(k+2,TimeUnit.Years)); + double? nullstrike = null; + double cashRate = 0.01; + double gap = 1e-4; + DigitalReplication replication = new DigitalReplication(Replication.Type.Central, gap); + Date paymentDate = endDate; + + FloatingRateCoupon underlying = new IborCoupon(paymentDate, vars.nominal,startDate, endDate, + vars.fixingDays, vars.index,gearing, spread); + // Floating Rate Coupon - Deep-in-the-money Call Digital option + double strike = 0.001; + DigitalCoupon digitalCappedCoupon = new DigitalCoupon(underlying,strike, Position.Type.Short, false, + cashRate,nullstrike, Position.Type.Short, false, nullstrike,replication); + IborCouponPricer pricer = new BlackIborCouponPricer(volatility); + digitalCappedCoupon.setPricer(pricer); + + // Check price vs its target + double accrualPeriod = underlying.accrualPeriod(); + double discount = vars.termStructure.link.discount(endDate); + + double targetOptionPrice = cashRate * vars.nominal * accrualPeriod * discount; + double targetPrice = underlying.price(vars.termStructure) - targetOptionPrice; + double digitalPrice = digitalCappedCoupon.price(vars.termStructure); + + double error = Math.Abs(targetPrice - digitalPrice); + double tolerance = 1e-07; + if (error>tolerance) + Assert.Fail("\nFloating Coupon - Digital Call Coupon:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nCoupon price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError " + error ); + + // Check digital option price + double replicationOptionPrice = digitalCappedCoupon.callOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + double optionTolerance = 1e-07; + if (error>optionTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + replicationOptionPrice + + "\nTarget price = " + targetOptionPrice + + "\nError = " + error); + + // Floating Rate Coupon + Deep-in-the-money Put Digital option + strike = 0.99; + DigitalCoupon digitalFlooredCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, false, + nullstrike,strike, Position.Type.Long, false, cashRate,replication); + digitalFlooredCoupon.setPricer(pricer); + + // Check price vs its target + targetPrice = underlying.price(vars.termStructure) + targetOptionPrice; + digitalPrice = digitalFlooredCoupon.price(vars.termStructure); + error = Math.Abs(targetPrice - digitalPrice); + if (error>tolerance) + Assert.Fail("\nFloating Coupon + Digital Put Option:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nCoupon price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError = " + error ); + + // Check digital option + replicationOptionPrice = digitalFlooredCoupon.putOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + if (error>optionTolerance) + Assert.Fail("\nDigital Put Coupon:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + replicationOptionPrice + + "\nTarget price = " + targetOptionPrice + + "\nError = " + error ); + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testCashOrNothingDeepOutTheMoney() + { + // Testing European deep out-the-money cash-or-nothing digital coupon + CommonVars vars = new CommonVars(); + + double gearing = 1.0; + double spread = 0.0; + + double capletVolatility = 0.0001; + RelinkableHandle volatility = new RelinkableHandle(); + volatility.linkTo(new ConstantOptionletVolatility(vars.today, vars.calendar, BusinessDayConvention.Following, + capletVolatility, new Actual360())); + + for (int k = 0; k<10; k++) + { + // loop on start and end dates + Date startDate = vars.calendar.advance(vars.settlement,new Period(k+1,TimeUnit.Years)); + Date endDate = vars.calendar.advance(vars.settlement,new Period(k+2,TimeUnit.Years)); + double? nullstrike = null; + double cashRate = 0.01; + double gap = 1e-4; + DigitalReplication replication = new DigitalReplication(Replication.Type.Central, gap); + Date paymentDate = endDate; + + FloatingRateCoupon underlying = new IborCoupon(paymentDate, vars.nominal,startDate, endDate, + vars.fixingDays, vars.index,gearing, spread); + // Deep out-of-the-money Capped Digital Coupon + double strike = 0.99; + DigitalCoupon digitalCappedCoupon = new DigitalCoupon(underlying,strike, Position.Type.Short, false, + cashRate,nullstrike, Position.Type.Short, false, nullstrike,replication); + + IborCouponPricer pricer = new BlackIborCouponPricer(volatility); + digitalCappedCoupon.setPricer(pricer); + + // Check price vs its target + double accrualPeriod = underlying.accrualPeriod(); + double discount = vars.termStructure.link.discount(endDate); + + double targetPrice = underlying.price(vars.termStructure); + double digitalPrice = digitalCappedCoupon.price(vars.termStructure); + double error = Math.Abs(targetPrice - digitalPrice); + double tolerance = 1e-10; + if (error>tolerance) + Assert.Fail("\nFloating Coupon + Digital Call Option:" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nCoupon price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError = " + error ); + + // Check digital option price + double targetOptionPrice = 0.0; + double replicationOptionPrice = digitalCappedCoupon.callOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + double optionTolerance = 1e-10; + if (error>optionTolerance) + Assert.Fail("\nDigital Call Option:" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication = " + replicationOptionPrice + + "\nTarget price = " + targetOptionPrice + + "\nError = " + error ); + + // Deep out-of-the-money Floored Digital Coupon + strike = 0.01; + DigitalCoupon digitalFlooredCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, false, + nullstrike,strike, Position.Type.Long, false, cashRate,replication); + digitalFlooredCoupon.setPricer(pricer); + + // Check price vs its target + targetPrice = underlying.price(vars.termStructure); + digitalPrice = digitalFlooredCoupon.price(vars.termStructure); + tolerance = 1e-09; + error = Math.Abs(targetPrice - digitalPrice); + if (error>tolerance) + Assert.Fail("\nDigital Floored Coupon:" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nCoupon price = " + digitalPrice + + "\nTarget price = " + targetPrice + + "\nError = " + error ); + + // Check digital option + targetOptionPrice = 0.0; + replicationOptionPrice = digitalFlooredCoupon.putOptionRate() * + vars.nominal * accrualPeriod * discount; + error = Math.Abs(targetOptionPrice - replicationOptionPrice); + if (error>optionTolerance) + Assert.Fail("\nDigital Put Option:" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice by replication " + replicationOptionPrice + + "\nTarget price " + targetOptionPrice + + "\nError " + error ); + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testCallPutParity() + { + // Testing call/put parity for European digital coupon + CommonVars vars = new CommonVars(); + + double[] vols = { 0.05, 0.15, 0.30 }; + double[] strikes = { 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 }; + + double gearing = 1.0; + double spread = 0.0; + + double gap = 1e-04; + DigitalReplication replication = new DigitalReplication(Replication.Type.Central, gap); + + for (int i = 0; i< vols.Length; i++) + { + double capletVolatility = vols[i]; + RelinkableHandle volatility = new RelinkableHandle(); + volatility.linkTo(new ConstantOptionletVolatility(vars.today, vars.calendar, BusinessDayConvention.Following, + capletVolatility, new Actual360())); + for (int j = 0; j< strikes.Length; j++) + { + double strike = strikes[j]; + for (int k = 0; k<10; k++) + { + Date startDate = vars.calendar.advance(vars.settlement,new Period(k+1,TimeUnit.Years)); + Date endDate = vars.calendar.advance(vars.settlement,new Period(k+2,TimeUnit.Years)); + double? nullstrike = null; + + Date paymentDate = endDate; + + FloatingRateCoupon underlying = new IborCoupon(paymentDate, vars.nominal,startDate, endDate, + vars.fixingDays, vars.index,gearing, spread); + // Cash-or-Nothing + double cashRate = 0.01; + // Floating Rate Coupon + Call Digital option + DigitalCoupon cash_digitalCallCoupon = new DigitalCoupon(underlying,strike, Position.Type.Long, false, + cashRate,nullstrike, Position.Type.Long, false, nullstrike,replication); + IborCouponPricer pricer = new BlackIborCouponPricer(volatility); + cash_digitalCallCoupon.setPricer(pricer); + // Floating Rate Coupon - Put Digital option + DigitalCoupon cash_digitalPutCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, + false, nullstrike,strike, Position.Type.Short, false, cashRate,replication); + + cash_digitalPutCoupon.setPricer(pricer); + double digitalPrice = cash_digitalCallCoupon.price(vars.termStructure) - + cash_digitalPutCoupon.price(vars.termStructure); + // Target price + double accrualPeriod = underlying.accrualPeriod(); + double discount = vars.termStructure.link.discount(endDate); + double targetPrice = vars.nominal * accrualPeriod * discount * cashRate; + + double error = Math.Abs(targetPrice - digitalPrice); + double tolerance = 1.0e-08; + if (error>tolerance) + Assert.Fail("\nCash-or-nothing:" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice = " + digitalPrice + + "\nTarget Price = " + targetPrice + + "\nError = " + error ); + + // Asset-or-Nothing + // Floating Rate Coupon + Call Digital option + DigitalCoupon asset_digitalCallCoupon = new DigitalCoupon(underlying,strike, Position.Type.Long, false, + nullstrike,nullstrike, Position.Type.Long, false, nullstrike,replication); + asset_digitalCallCoupon.setPricer(pricer); + // Floating Rate Coupon - Put Digital option + DigitalCoupon asset_digitalPutCoupon = new DigitalCoupon(underlying,nullstrike, Position.Type.Long, + false, nullstrike,strike, Position.Type.Short, false, nullstrike,replication); + asset_digitalPutCoupon.setPricer(pricer); + digitalPrice = asset_digitalCallCoupon.price(vars.termStructure) - + asset_digitalPutCoupon.price(vars.termStructure); + // Target price + targetPrice = vars.nominal * accrualPeriod * discount * underlying.rate(); + error = Math.Abs(targetPrice - digitalPrice); + tolerance = 1.0e-07; + if (error>tolerance) + Assert.Fail("\nAsset-or-nothing:" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nPrice = " + digitalPrice + + "\nTarget Price = " + targetPrice + + "\nError = " + error ); + } + } + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testReplicationType() + { + // Testing replication type for European digital coupon + CommonVars vars = new CommonVars(); + + double[] vols = { 0.05, 0.15, 0.30 }; + double[] strikes = { 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 }; + + double gearing = 1.0; + double spread = 0.0; + + double gap = 1e-04; + DigitalReplication subReplication = new DigitalReplication(Replication.Type.Sub, gap); + DigitalReplication centralReplication = new DigitalReplication(Replication.Type.Central, gap); + DigitalReplication superReplication = new DigitalReplication(Replication.Type.Super, gap); + + for (int i = 0; i< vols.Length; i++) + { + double capletVolatility = vols[i]; + RelinkableHandle volatility = new RelinkableHandle(); + volatility.linkTo(new ConstantOptionletVolatility(vars.today, vars.calendar, BusinessDayConvention.Following, + capletVolatility, new Actual360())); + for (int j = 0; j< strikes.Length; j++) + { + double strike = strikes[j]; + for (int k = 0; k<10; k++) + { + Date startDate = vars.calendar.advance(vars.settlement,new Period(k+1,TimeUnit.Years)); + Date endDate = vars.calendar.advance(vars.settlement,new Period(k+2,TimeUnit.Years)); + double? nullstrike = null; + + Date paymentDate = endDate; + + FloatingRateCoupon underlying = new IborCoupon(paymentDate, vars.nominal,startDate, endDate, + vars.fixingDays, vars.index,gearing, spread); + // Cash-or-Nothing + double cashRate = 0.005; + // Floating Rate Coupon + Call Digital option + DigitalCoupon sub_cash_longDigitalCallCoupon = new DigitalCoupon(underlying,strike, Position.Type.Long, + false, cashRate,nullstrike, Position.Type.Long, false, nullstrike,subReplication); + DigitalCoupon central_cash_longDigitalCallCoupon = new DigitalCoupon(underlying,strike, + Position.Type.Long, false, cashRate,nullstrike, Position.Type.Long, false, nullstrike, + centralReplication); + DigitalCoupon over_cash_longDigitalCallCoupon = new DigitalCoupon(underlying,strike, Position.Type.Long, + false, cashRate,nullstrike, Position.Type.Long, false, nullstrike,superReplication); + IborCouponPricer pricer = new BlackIborCouponPricer(volatility); + sub_cash_longDigitalCallCoupon.setPricer(pricer); + central_cash_longDigitalCallCoupon.setPricer(pricer); + over_cash_longDigitalCallCoupon.setPricer(pricer); + double sub_digitalPrice = sub_cash_longDigitalCallCoupon.price(vars.termStructure); + double central_digitalPrice = central_cash_longDigitalCallCoupon.price(vars.termStructure); + double over_digitalPrice = over_cash_longDigitalCallCoupon.price(vars.termStructure); + double tolerance = 1.0e-09; + if ( ( (sub_digitalPrice > central_digitalPrice) && + Math.Abs(central_digitalPrice - sub_digitalPrice)>tolerance ) || + ( (central_digitalPrice>over_digitalPrice) && + Math.Abs(central_digitalPrice - over_digitalPrice)>tolerance ) ) + { + Assert.Fail("\nCash-or-nothing: Floating Rate Coupon + Call Digital option" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nSub-Replication Price = " + sub_digitalPrice + + "\nCentral-Replication Price = " + central_digitalPrice + + "\nOver-Replication Price = " + over_digitalPrice); + } + + // Floating Rate Coupon - Call Digital option + DigitalCoupon sub_cash_shortDigitalCallCoupon = new DigitalCoupon(underlying,strike, Position.Type.Short, + false, cashRate,nullstrike, Position.Type.Long, false, nullstrike,subReplication); + DigitalCoupon central_cash_shortDigitalCallCoupon = new DigitalCoupon(underlying,strike, + Position.Type.Short, false, cashRate,nullstrike, Position.Type.Long, false, nullstrike, + centralReplication); + DigitalCoupon over_cash_shortDigitalCallCoupon = new DigitalCoupon(underlying,strike, + Position.Type.Short, false, cashRate,nullstrike, Position.Type.Long, false, nullstrike, + superReplication); + sub_cash_shortDigitalCallCoupon.setPricer(pricer); + central_cash_shortDigitalCallCoupon.setPricer(pricer); + over_cash_shortDigitalCallCoupon.setPricer(pricer); + sub_digitalPrice = sub_cash_shortDigitalCallCoupon.price(vars.termStructure); + central_digitalPrice = central_cash_shortDigitalCallCoupon.price(vars.termStructure); + over_digitalPrice = over_cash_shortDigitalCallCoupon.price(vars.termStructure); + if ( ( (sub_digitalPrice > central_digitalPrice) && + Math.Abs(central_digitalPrice - sub_digitalPrice)>tolerance ) || + ( (central_digitalPrice>over_digitalPrice) && + Math.Abs(central_digitalPrice - over_digitalPrice)>tolerance ) ) + { + Assert.Fail("\nCash-or-nothing: Floating Rate Coupon - Call Digital option" + + "\nVolatility = " + +(capletVolatility) + + "\nStrike = " + +(strike) + + "\nExercise = " + k+1 + " years" + + "\nSub-Replication Price = " + sub_digitalPrice + + "\nCentral-Replication Price = " + central_digitalPrice + + "\nOver-Replication Price = " + over_digitalPrice); + } + // Floating Rate Coupon + Put Digital option + DigitalCoupon sub_cash_longDigitalPutCoupon = new DigitalCoupon(underlying,nullstrike, + Position.Type.Long, false, nullstrike,strike, Position.Type.Long, false, cashRate,subReplication); + DigitalCoupon central_cash_longDigitalPutCoupon = new DigitalCoupon(underlying,nullstrike, + Position.Type.Long, false, nullstrike,strike, Position.Type.Long, false, cashRate,centralReplication); + DigitalCoupon over_cash_longDigitalPutCoupon= new DigitalCoupon(underlying,nullstrike, + Position.Type.Long, false, nullstrike,strike, Position.Type.Long, false, cashRate,superReplication); + sub_cash_longDigitalPutCoupon.setPricer(pricer); + central_cash_longDigitalPutCoupon.setPricer(pricer); + over_cash_longDigitalPutCoupon.setPricer(pricer); + sub_digitalPrice = sub_cash_longDigitalPutCoupon.price(vars.termStructure); + central_digitalPrice = central_cash_longDigitalPutCoupon.price(vars.termStructure); + over_digitalPrice = over_cash_longDigitalPutCoupon.price(vars.termStructure); + if ( ( (sub_digitalPrice > central_digitalPrice) && + Math.Abs(central_digitalPrice - sub_digitalPrice)>tolerance ) || + ( (central_digitalPrice>over_digitalPrice) && + Math.Abs(central_digitalPrice - over_digitalPrice)>tolerance ) ) + { + Assert.Fail("\nCash-or-nothing: Floating Rate Coupon + Put Digital option" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nSub-Replication Price = " + sub_digitalPrice + + "\nCentral-Replication Price = " + central_digitalPrice + + "\nOver-Replication Price = " + over_digitalPrice); + } + + // Floating Rate Coupon - Put Digital option + DigitalCoupon sub_cash_shortDigitalPutCoupon = new DigitalCoupon(underlying,nullstrike, + Position.Type.Long, false, nullstrike,strike, Position.Type.Short, false, cashRate,subReplication); + DigitalCoupon central_cash_shortDigitalPutCoupon= new DigitalCoupon(underlying,nullstrike, + Position.Type.Long, false, nullstrike,strike, Position.Type.Short, false, cashRate,centralReplication); + DigitalCoupon over_cash_shortDigitalPutCoupon = new DigitalCoupon(underlying,nullstrike, + Position.Type.Long, false, nullstrike,strike, Position.Type.Short, false, cashRate,superReplication); + sub_cash_shortDigitalPutCoupon.setPricer(pricer); + central_cash_shortDigitalPutCoupon.setPricer(pricer); + over_cash_shortDigitalPutCoupon.setPricer(pricer); + sub_digitalPrice = sub_cash_shortDigitalPutCoupon.price(vars.termStructure); + central_digitalPrice = central_cash_shortDigitalPutCoupon.price(vars.termStructure); + over_digitalPrice = over_cash_shortDigitalPutCoupon.price(vars.termStructure); + if ( ( (sub_digitalPrice > central_digitalPrice) && + Math.Abs(central_digitalPrice - sub_digitalPrice)>tolerance ) || + ( (central_digitalPrice>over_digitalPrice) && + Math.Abs(central_digitalPrice - over_digitalPrice)>tolerance ) ) + { + Assert.Fail("\nCash-or-nothing: Floating Rate Coupon + Call Digital option" + + "\nVolatility = " + (capletVolatility) + + "\nStrike = " + (strike) + + "\nExercise = " + k+1 + " years" + + "\nSub-Replication Price = " + sub_digitalPrice + + "\nCentral-Replication Price = " + central_digitalPrice + + "\nOver-Replication Price = " + over_digitalPrice); + } + } + } + } + } + } +} diff --git a/Test/T_DoubleBarrierOption.cs b/Test/T_DoubleBarrierOption.cs new file mode 100644 index 000000000..fc4996d22 --- /dev/null +++ b/Test/T_DoubleBarrierOption.cs @@ -0,0 +1,507 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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. +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; +using System; +using System.Collections.Generic; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_DoubleBarrierOption + { + public void REPORT_FAILURE( string greekName, DoubleBarrier.Type barrierType, double barrierlo, double barrierhi, + StrikedTypePayoff payoff, Exercise exercise, double s, double q, + double r, Date today, double v, double expected, double calculated, double error, + double tolerance ) + { + QAssert.Fail( barrierType + " " + exercise + " " + + payoff.optionType() + " option with " + + payoff + " payoff:\n" + + " underlying value: " + s + "\n" + + " strike: " + payoff.strike() + "\n" + + " barrier low: " + barrierlo + "\n" + + " barrier high: " + barrierhi + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + public void REPORT_FAILURE_VANNAVOLGA( string greekName, DoubleBarrier.Type barrierType, + double barrier1, double barrier2,double rebate, + StrikedTypePayoff payoff, Exercise exercise, double s, double q, + double r, Date today, double vol25Put, double atmVol, double vol25Call, double v, + double expected, double calculated, double error, double tolerance ) + { + QAssert.Fail( "Double Barrier Option " + barrierType + " " + exercise + " " + + payoff.optionType() + " option with " + + payoff + " payoff:\n" + + " underlying value: " + s + "\n" + + " strike: " + payoff.strike() + "\n" + + " barrier 1: " + barrier1 + "\n" + + " barrier 2: " + barrier2 + "\n" + + " rebate : " + rebate + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " 25PutVol: " + +(vol25Put) + "\n" + + " atmVol: " + (atmVol) + "\n" + + " 25CallVol: " + (vol25Call) + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + + private class NewBarrierOptionData + { + public DoubleBarrier.Type barrierType; + public double barrierlo; + public double barrierhi; + public Option.Type type; + public Exercise.Type exType; + public double strike; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double t; // time to maturity + public double v; // volatility + public double result; // result + public double tol; // tolerance + + public NewBarrierOptionData(DoubleBarrier.Type barrierType, double barrierlo, double barrierhi, Option.Type type, Exercise.Type exType, double strike, double s, double q, double r, double t, double v, double result, double tol) + { + this.barrierType = barrierType; + this.barrierlo = barrierlo; + this.barrierhi = barrierhi; + this.type = type; + this.exType = exType; + this.strike = strike; + this.s = s; + this.q = q; + this.r = r; + this.t = t; + this.v = v; + this.result = result; + this.tol = tol; + } + } + + private class DoubleBarrierFxOptionData + { + public DoubleBarrier.Type barrierType; + public double barrier1; + public double barrier2; + public double rebate; + public Option.Type type; + public double strike; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double t; // time to maturity + public double vol25Put; // 25 delta put vol + public double volAtm; // atm vol + public double vol25Call; // 25 delta call vol + public double v; // volatility at strike + public double result; // result + public double tol; // tolerance + + public DoubleBarrierFxOptionData(DoubleBarrier.Type barrierType, double barrier1, double barrier2, double rebate, Option.Type type, double strike, double s, double q, double r, double t, double vol25Put, double volAtm, double vol25Call, double v, double result, double tol) + { + this.barrierType = barrierType; + this.barrier1 = barrier1; + this.barrier2 = barrier2; + this.rebate = rebate; + this.type = type; + this.strike = strike; + this.s = s; + this.q = q; + this.r = r; + this.t = t; + this.vol25Put = vol25Put; + this.volAtm = volAtm; + this.vol25Call = vol25Call; + this.v = v; + this.result = result; + this.tol = tol; + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testEuropeanHaugValues() + { + // Testing double barrier european options against Haug's values + + Exercise.Type european = Exercise.Type.European; + NewBarrierOptionData[] values = + { + /* The data below are from + "The complete guide to option pricing formulas 2nd Ed",E.G. Haug, McGraw-Hill, p.156 and following. + + Note: + The book uses b instead of q (q=r-b) + */ + // BarrierType, barr.lo, barr.hi, type, exercise,strk, s, q, r, t, v, result, tol + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 4.3515, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 6.1644, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 7.0373, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 6.9853, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 7.9336, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 6.5088, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 4.3505, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 5.8500, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 5.7726, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 6.8082, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 6.3383, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 4.3841, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 4.3139, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 4.8293, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 3.7765, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 5.9697, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 4.0004, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 2.2563, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 3.7516, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 2.6387, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 1.4903, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 3.5805, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 1.5098, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 0.5635, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 1.2055, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 0.3098, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 0.0477, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 0.5537, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 0.0441, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 0.0011, 1.0e-4), + + // BarrierType, barr.lo, barr.hi, type, exercise,strk, s, q, r, t, v, result, tol + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 1.8825, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 3.7855, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 5.7191, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 2.1374, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 4.7033, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 50.0, 150.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 7.1683, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 1.8825, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 3.7845, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 5.6060, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 2.1374, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 4.6236, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 60.0, 140.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 6.1062, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 1.8825, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 3.7014, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 4.6472, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 2.1325, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 3.8944, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 70.0, 130.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 3.5868, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 1.8600, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 2.6866, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 2.0719, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 1.8883, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 1.7851, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 80.0, 120.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 0.8244, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 0.9473, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 0.3449, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 0.0578, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 0.4555, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 0.0491, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockOut, 90.0, 110.0, Option.Type.Put, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 0.0013, 1.0e-4), + + // BarrierType, barr.lo, barr.hi, type, strk, s, q, r, t, v, result, tol + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 0.0000, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 0.0900, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 1.1537, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 0.0292, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 1.6487, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 50.0, 150.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 5.7321, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 0.0010, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 0.4045, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 2.4184, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 0.2062, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 3.2439, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 60.0, 140.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 7.8569, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 0.0376, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 1.4252, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 4.4145, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 1.0447, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 5.5818, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 70.0, 130.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 9.9846, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 0.5999, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 3.6158, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 6.7007, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 3.4340, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 8.0724, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 80.0, 120.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 11.6774, 1.0e-4), + + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.15, 3.1460, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.25, 5.9447, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.25, 0.35, 8.1432, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.15, 6.4608, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.25, 9.5382, 1.0e-4), + new NewBarrierOptionData( DoubleBarrier.Type.KnockIn, 90.0, 110.0, Option.Type.Call, european, 100, 100.0, 0.0, 0.1, 0.50, 0.35, 12.2398, 1.0e-4), + + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + DoubleBarrierOption opt = new DoubleBarrierOption(values[i].barrierType,values[i].barrierlo, + values[i].barrierhi,0, // no rebate + payoff, exercise); + + // Ikeda/Kunitomo engine + IPricingEngine engine = new AnalyticDoubleBarrierEngine(stochProcess); + opt.setPricingEngine(engine); + + double calculated = opt.NPV(); + double expected = values[i].result; + double error = Math.Abs(calculated-expected); + if (error>values[i].tol) + { + REPORT_FAILURE("Ikeda/Kunitomo value", values[i].barrierType, values[i].barrierlo, + values[i].barrierhi, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].v, + expected, calculated, error, values[i].tol); + } + + // Wulin Suo/Yong Wang engine + engine = new WulinYongDoubleBarrierEngine( stochProcess ); + opt.setPricingEngine( engine ); + + calculated = opt.NPV(); + expected = values[i].result; + error = Math.Abs( calculated - expected ); + if ( error > values[i].tol ) + { + REPORT_FAILURE( "Wulin/Yong value", values[i].barrierType, values[i].barrierlo, + values[i].barrierhi, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].v, + expected, calculated, error, values[i].tol ); + } + + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testVannaVolgaDoubleBarrierValues() + { + // Testing double-barrier FX options against Vanna/Volga values + SavedSettings backup = new SavedSettings(); + + DoubleBarrierFxOptionData[] values = + { + // BarrierType, barr.1, barr.2, rebate, type, strike, s, q, r, t, vol25Put, volAtm,vol25Call, vol, result, tol + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.13321, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.11638, 0.14413, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.22687, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.10088, 0.07456, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.31179, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08925, 0.02710, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.38843, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08463, 0.00569, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Call, 1.46047, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08412, 0.00013, 1.0e-4), + + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put, 1.13321, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.11638, 0.00017, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put, 1.22687, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.10088, 0.00353, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put, 1.31179, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08925, 0.02221, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put, 1.38843, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08463, 0.06049, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.1, 1.5, 0.0, Option.Type.Put, 1.46047, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08412, 0.11103, 1.0e-4), + + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Call, 1.13321, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.11638, 0.14486, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Call, 1.22687, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.10088, 0.07534, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Call, 1.31179, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08925, 0.02707, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Call, 1.38843, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08463, 0.00536, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Call, 1.46047, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08412, 4.14862e-005, 1.0e-4), + + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Put, 1.13321, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.11638, 0.00095, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Put, 1.22687, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.10088, 0.00437, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Put, 1.31179, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08925, 0.02224, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Put, 1.38843, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08463, 0.06021, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.1, 1.5, 0.0, Option.Type.Put, 1.46047, 1.30265, 0.0003541, 0.0033871, 1.0, 0.10087, 0.08925, 0.08463, 0.08412, 0.11100, 1.0e-4), + + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.06145, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.12511, 0.19981, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.19545, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.10890, 0.10389, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.32238, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09444, 0.03555, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.44298, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09197, 0.00634, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Call, 1.56345, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09261, 0.00000, 1.0e-4), + + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put, 1.06145, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.12511, 0.00000, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put, 1.19545, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.10890, 0.00436, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put, 1.32238, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09444, 0.03173, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put, 1.44298, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09197, 0.09346, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockOut, 1.0, 1.6, 0.0, Option.Type.Put, 1.56345, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09261, 0.17704, 1.0e-4), + + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Call, 1.06145, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.12511, 0.20202, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Call, 1.19545, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.10890, 0.10521, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Call, 1.32238, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09444, 0.03589, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Call, 1.44298, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09197, 0.00601, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Call, 1.56345, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09261, 0.00000, 1.0e-4), + + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Put, 1.06145, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.12511, 0.00153, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Put, 1.19545, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.10890, 0.00578, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Put, 1.32238, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09444, 0.03218, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Put, 1.44298, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09197, 0.09325, 1.0e-4), + new DoubleBarrierFxOptionData( DoubleBarrier.Type.KnockIn, 1.0, 1.6, 0.0, Option.Type.Put, 1.56345, 1.30265, 0.0009418, 0.0039788, 2.0, 0.10891, 0.09525, 0.09197, 0.09261, 0.17804, 1.0e-4) + + }; + + DayCounter dc = new Actual360(); + Date today = new Date(05, Month.Mar, 2013); + Settings.setEvaluationDate(today); + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol25Put = new SimpleQuote(0.0); + SimpleQuote volAtm = new SimpleQuote(0.0); + SimpleQuote vol25Call = new SimpleQuote(0.0); + + for (int i=0; i volAtmQuote = new Handle( + new DeltaVolQuote( new Handle(volAtm),DeltaVolQuote.DeltaType.Fwd,values[i].t, + DeltaVolQuote.AtmType.AtmDeltaNeutral)); + + //always delta neutral atm + Handle vol25PutQuote = new Handle(new DeltaVolQuote(-0.25, + new Handle(vol25Put),values[i].t,DeltaVolQuote.DeltaType.Fwd)); + + Handle vol25CallQuote = new Handle(new DeltaVolQuote(0.25, + new Handle(vol25Call),values[i].t,DeltaVolQuote.DeltaType.Fwd)); + + DoubleBarrierOption doubleBarrierOption = new DoubleBarrierOption(values[i].barrierType, + values[i].barrier1,values[i].barrier2,values[i].rebate,payoff,exercise); + + double bsVanillaPrice = Utils.blackFormula(values[i].type, values[i].strike, + spot.value()*qTS.discount(values[i].t)/rTS.discount(values[i].t), + values[i].v * Math.Sqrt(values[i].t), rTS.discount(values[i].t)); + + IPricingEngine vannaVolgaEngine; + + vannaVolgaEngine = new VannaVolgaDoubleBarrierEngine( volAtmQuote, vol25PutQuote, vol25CallQuote, + new Handle( spot ), + new Handle( rTS ), + new Handle( qTS ), + ( process, series ) => new WulinYongDoubleBarrierEngine( process, series ), + true, + bsVanillaPrice ); + doubleBarrierOption.setPricingEngine( vannaVolgaEngine ); + + double calculated = doubleBarrierOption.NPV(); + double expected = values[i].result; + double error = Math.Abs( calculated - expected ); + if ( error > values[i].tol ) + { + REPORT_FAILURE_VANNAVOLGA( "value", values[i].barrierType, + values[i].barrier1, values[i].barrier2, + values[i].rebate, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].vol25Put, + values[i].volAtm, values[i].vol25Call, values[i].v, + expected, calculated, error, values[i].tol ); + } + + vannaVolgaEngine = new VannaVolgaDoubleBarrierEngine(volAtmQuote,vol25PutQuote,vol25CallQuote, + new Handle (spot), + new Handle (rTS), + new Handle (qTS), + (process,series) => new AnalyticDoubleBarrierEngine(process,series), + true, + bsVanillaPrice); + doubleBarrierOption.setPricingEngine(vannaVolgaEngine); + + calculated = doubleBarrierOption.NPV(); + expected = values[i].result; + error = Math.Abs( calculated - expected ); + double maxtol = 5.0e-3; // different engines have somewhat different results + if (error>maxtol) + { + REPORT_FAILURE_VANNAVOLGA( "value", values[i].barrierType, + values[i].barrier1, values[i].barrier2, + values[i].rebate, payoff, exercise, values[i].s, + values[i].q, values[i].r, today, values[i].vol25Put, + values[i].volAtm, values[i].vol25Call, values[i].v, + expected, calculated, error, values[i].tol); + } + } + } + } +} diff --git a/Test/T_ForwardOption.cs b/Test/T_ForwardOption.cs new file mode 100644 index 000000000..a9d35498e --- /dev/null +++ b/Test/T_ForwardOption.cs @@ -0,0 +1,538 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ + #if QL_DOTNET_FRAMEWORK + [TestClass()] + #endif + public class T_ForwardOption + { + void REPORT_FAILURE( string greekName, + StrikedTypePayoff payoff, + Exercise exercise, + double s, + double q, + double r, + Date today, + double v, + double moneyness, + Date reset, + double expected, + double calculated, + double error, + double tolerance) + { + QAssert.Fail("Forward " +exercise + " " + + payoff.optionType() + " option with " + + payoff + " payoff:\n" + + " spot value: " + s + "\n" + + " strike: " + payoff.strike() + "\n" + + " moneyness: " + moneyness + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " reset date: " + reset + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance); + } + + + public class ForwardOptionData + { + public ForwardOptionData(Option.Type type_,double moneyness_,double s_,double q_,double r_,double start_, + double t_,double v_,double result_,double tol_) + { + type = type_; + moneyness = moneyness_; + s = s_; + q = q_; + r = r_; + start = start_; + t = t_; + v = v_; + result = result_; + tol = tol_; + } + + public Option.Type type; + public double moneyness; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double start; // time to reset + public double t; // time to maturity + public double v; // volatility + public double result; // expected result + public double tol; // tolerance + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testValues() + { + // Testing forward option values... + + /* The data below are from + "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 + */ + ForwardOptionData[] values = + { + // type, moneyness, spot, div, rate,start, t, vol, result, tol + // "Option pricing formulas", pag. 37 + new ForwardOptionData(Option.Type.Call, 1.1, 60.0, 0.04, 0.08, 0.25, 1.0, 0.30, 4.4064, 1.0e-4), + // "Option pricing formulas", VBA code + new ForwardOptionData(Option.Type.Put, 1.1, 60.0, 0.04, 0.08, 0.25, 1.0, 0.30, 8.2971, 1.0e-4) + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + Handle qTS = new Handle(Utilities.flatRate(today, qRate, dc)); + SimpleQuote rRate = new SimpleQuote(0.0); + Handle rTS = new Handle(Utilities.flatRate(today, rRate, dc)); + SimpleQuote vol = new SimpleQuote(0.0); + Handle volTS = new Handle(Utilities.flatVol(today, vol, dc)); + + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(new Handle(spot), + new Handle(qTS),new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new ForwardVanillaEngine( stochProcess, process => new AnalyticEuropeanEngine(process)); // AnalyticEuropeanEngine + + for (int i=0; i< values.Length; i++) + { + + StrikedTypePayoff payoff = new PlainVanillaPayoff(values[i].type, 0.0); + Date exDate = today + Convert.ToInt32(values[i].t*360+0.5); + Exercise exercise = new EuropeanExercise(exDate); + Date reset = today + Convert.ToInt32(values[i].start*360+0.5); + + spot .setValue(values[i].s); + qRate.setValue(values[i].q); + rRate.setValue(values[i].r); + vol .setValue(values[i].v); + + ForwardVanillaOption option = new ForwardVanillaOption(values[i].moneyness, reset,payoff, exercise); + option.setPricingEngine(engine); + + double calculated = option.NPV(); + double error = Math.Abs(calculated-values[i].result); + double tolerance = 1e-4; + if (error>tolerance) + { + REPORT_FAILURE("value", payoff, exercise, values[i].s, + values[i].q, values[i].r, today, + values[i].v, values[i].moneyness, reset, + values[i].result, calculated, + error, tolerance); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testPerformanceValues() + { + // Testing forward performance option values... + + /* The data below are the performance equivalent of the + forward options tested above and taken from + "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 + */ + ForwardOptionData[] values = { + // type, moneyness, spot, div, rate,start, maturity, vol, result, tol + new ForwardOptionData(Option.Type.Call, 1.1, 60.0, 0.04, 0.08, 0.25, 1.0, 0.30, 4.4064/60*Math.Exp(-0.04*0.25), 1.0e-4 ), + new ForwardOptionData(Option.Type.Put, 1.1, 60.0, 0.04, 0.08, 0.25, 1.0, 0.30, 8.2971/60*Math.Exp(-0.04*0.25), 1.0e-4 ) + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + Handle qTS = new Handle(Utilities.flatRate(today, qRate, dc)); + SimpleQuote rRate = new SimpleQuote(0.0); + Handle rTS = new Handle(Utilities.flatRate(today, rRate, dc)); + SimpleQuote vol = new SimpleQuote(0.0); + Handle volTS = new Handle(Utilities.flatVol(today, vol, dc)); + + BlackScholesMertonProcess stochProcess= new BlackScholesMertonProcess(new Handle(spot), + new Handle(qTS),new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new ForwardPerformanceVanillaEngine( stochProcess, process => new AnalyticEuropeanEngine( process ) ); // AnalyticEuropeanEngine + + for (int i=0; itolerance) + { + REPORT_FAILURE("value", payoff, exercise, values[i].s, + values[i].q, values[i].r, today, + values[i].v, values[i].moneyness, reset, + values[i].result, calculated, + error, tolerance); + } + } + } + + private void testForwardGreeks(Type engine_type) + { + Dictionary calculated = new Dictionary(), + expected = new Dictionary(), + tolerance = new Dictionary(); + tolerance["delta"] = 1.0e-5; + tolerance["gamma"] = 1.0e-5; + tolerance["theta"] = 1.0e-5; + tolerance["rho"] = 1.0e-5; + tolerance["divRho"] = 1.0e-5; + tolerance["vega"] = 1.0e-5; + + Option.Type[] types = { Option.Type.Call, Option.Type.Put }; + double[] moneyness = { 0.9, 1.0, 1.1 }; + double[] underlyings = { 100.0 }; + double[] qRates = { 0.04, 0.05, 0.06 }; + double[] rRates = { 0.01, 0.05, 0.15 }; + int[] lengths = { 1, 2 }; + int[] startMonths = { 6, 9 }; + double[] vols = { 0.11, 0.50, 1.20 }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + Settings.setEvaluationDate(today); + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + Handle qTS = new Handle(Utilities.flatRate(qRate, dc)); + SimpleQuote rRate = new SimpleQuote(0.0); + Handle rTS = new Handle(Utilities.flatRate(rRate, dc)); + SimpleQuote vol = new SimpleQuote(0.0); + Handle volTS = new Handle(Utilities.flatVol(vol, dc)); + + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(new Handle(spot), qTS, rTS, volTS); + + IPricingEngine engine = engine_type == typeof( ForwardVanillaEngine ) ? new ForwardVanillaEngine( stochProcess, process => new AnalyticEuropeanEngine( process ) ) : + new ForwardPerformanceVanillaEngine( stochProcess, process => new AnalyticEuropeanEngine( process ) ); + + for (int i=0; i spot.value()*1.0e-5) + { + // perturb spot and get delta and gamma + double du = u*1.0e-4; + spot.setValue(u+du); + double value_p = option.NPV(), + delta_p = option.delta(); + spot.setValue(u-du); + double value_m = option.NPV(), + delta_m = option.delta(); + spot.setValue(u); + expected["delta"] = (value_p - value_m)/(2*du); + expected["gamma"] = (delta_p - delta_m)/(2*du); + + // perturb rates and get rho and dividend rho + double dr = r*1.0e-4; + rRate.setValue(r+dr); + value_p = option.NPV(); + rRate.setValue(r-dr); + value_m = option.NPV(); + rRate.setValue(r); + expected["rho"] = (value_p - value_m)/(2*dr); + + double dq = q*1.0e-4; + qRate.setValue(q+dq); + value_p = option.NPV(); + qRate.setValue(q-dq); + value_m = option.NPV(); + qRate.setValue(q); + expected["divRho"] = (value_p - value_m)/(2*dq); + + // perturb volatility and get vega + double dv = v*1.0e-4; + vol.setValue(v+dv); + value_p = option.NPV(); + vol.setValue(v-dv); + value_m = option.NPV(); + vol.setValue(v); + expected["vega"] = (value_p - value_m)/(2*dv); + + // perturb date and get theta + double dT = dc.yearFraction(today-1, today+1); + Settings.setEvaluationDate(today-1); + value_m = option.NPV(); + Settings.setEvaluationDate(today+1); + value_p = option.NPV(); + Settings.setEvaluationDate(today); + expected["theta"] = (value_p - value_m)/dT; + + // compare + //std::map::iterator it; + foreach (KeyValuePair it in calculated) + { + String greek = it.Key; + double expct = expected [greek], + calcl = calculated[greek], + tol = tolerance [greek]; + double error = Utilities.relativeError(expct,calcl,u); + if (error>tol) + { + REPORT_FAILURE(greek, payoff, exercise, + u, q, r, today, v, + moneyness[j], reset, + expct, calcl, error, tol); + } + } + } + } + } + } + } + } + } + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testGreeks() + { + // Testing forward option greeks + SavedSettings backup = new SavedSettings(); + + testForwardGreeks(typeof(ForwardVanillaEngine)); + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testPerformanceGreeks() + { + // Testing forward performance option greeks + SavedSettings backup = new SavedSettings(); + + testForwardGreeks(typeof(ForwardPerformanceVanillaEngine)); + } + + class TestBinomialEngine : BinomialVanillaEngine + { + public TestBinomialEngine(GeneralizedBlackScholesProcess process): + base(process, 300) // fixed steps + {} + } + + // verify than if engine + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testGreeksInitialization() + { + // Testing forward option greeks initialization + DayCounter dc = new Actual360(); + SavedSettings backup = new SavedSettings(); + Date today = Date.Today; + Settings.setEvaluationDate(today); + + SimpleQuote spot = new SimpleQuote(100.0); + SimpleQuote qRate = new SimpleQuote(0.04); + Handle qTS = new Handle(Utilities.flatRate(qRate, dc)); + SimpleQuote rRate = new SimpleQuote(0.01); + Handle rTS = new Handle(Utilities.flatRate(rRate, dc)); + SimpleQuote vol=new SimpleQuote(0.11); + Handle volTS = new Handle(Utilities.flatVol(vol, dc)); + + BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(new Handle(spot), qTS, rTS, volTS); + + IPricingEngine engine = new ForwardVanillaEngine( stochProcess, process => new TestBinomialEngine( process ) ); + Date exDate = today + new Period(1,TimeUnit.Years); + Exercise exercise = new EuropeanExercise(exDate); + Date reset = today + new Period(6,TimeUnit.Months); + StrikedTypePayoff payoff = new PlainVanillaPayoff(Option.Type.Call, 0.0); + + ForwardVanillaOption option = new ForwardVanillaOption(0.9, reset, payoff, exercise); + option.setPricingEngine(engine); + + IPricingEngine ctrlengine = new TestBinomialEngine(stochProcess); + VanillaOption ctrloption = new VanillaOption(payoff, exercise); + ctrloption.setPricingEngine(ctrlengine); + + double? delta = 0; + try + { + delta = ctrloption.delta(); + } + catch (Exception) + { + // if normal option can't calculate delta, + // nor should forward + try + { + delta = option.delta(); + } + catch (Exception) + { + delta = null; + } + Utils.QL_REQUIRE(delta == null,()=> "Forward delta invalid"); + } + + double? rho = 0; + try + { + rho = ctrloption.rho(); + } + catch (Exception) + { + // if normal option can't calculate rho, + // nor should forward + try + { + rho = option.rho(); + } + catch (Exception) + { + rho = null; + } + Utils.QL_REQUIRE(rho == null,()=> "Forward rho invalid"); + } + + double? divRho = 0; + try + { + divRho = ctrloption.dividendRho(); + } + catch (Exception) + { + // if normal option can't calculate divRho, + // nor should forward + try + { + divRho = option.dividendRho(); + } + catch (Exception) + { + divRho = null; + } + Utils.QL_REQUIRE(divRho == null,()=> "Forward dividendRho invalid"); + } + + double? vega = 0; + try + { + vega = ctrloption.vega(); + } + catch (Exception) + { + // if normal option can't calculate vega, + // nor should forward + try + { + vega = option.vega(); + } + catch (Exception) + { + vega = null; + } + Utils.QL_REQUIRE(vega == null,()=> "Forward vega invalid"); + } + } + } +} diff --git a/Test/T_InflationCPICapFloor.cs b/Test/T_InflationCPICapFloor.cs new file mode 100644 index 000000000..7943be26e --- /dev/null +++ b/Test/T_InflationCPICapFloor.cs @@ -0,0 +1,444 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_InflationCPICapFloor + { + internal struct Datum + { + public Date date; + public double rate; + + public Datum( Date d, double r ) + { + date = d; + rate = r; + } + } + + private class CommonVars + { + private List> makeHelpers( Datum[] iiData, int N, + ZeroInflationIndex ii, Period observationLag, + Calendar calendar, + BusinessDayConvention bdc, + DayCounter dc ) + { + List> instruments = new List>(); + for ( int i = 0; i < N; i++ ) + { + Date maturity = iiData[i].date; + Handle quote = new Handle( new SimpleQuote( iiData[i].rate / 100.0 ) ); + BootstrapHelper anInstrument = new ZeroCouponInflationSwapHelper( quote, observationLag, maturity, + calendar, bdc, dc, ii ); + instruments.Add( anInstrument ); + } + return instruments; + } + + // common data + public int length; + public Date startDate; + public double baseZeroRate; + public double volatility; + + public Frequency frequency; + public List nominals; + public Calendar calendar; + public BusinessDayConvention convention; + public int fixingDays; + public Date evaluationDate; + public int settlementDays; + public Date settlement; + public Period observationLag, contractObservationLag; + public InterpolationType contractObservationInterpolation; + public DayCounter dcZCIIS, dcNominal; + public List zciisD; + public List zciisR; + public UKRPI ii; + public RelinkableHandle hii; + public int zciisDataLength; + + public RelinkableHandle nominalUK; + public RelinkableHandle cpiUK; + public RelinkableHandle hcpi; + + public List cStrikesUK; + public List fStrikesUK; + public List cfMaturitiesUK; + public Matrix cPriceUK; + public Matrix fPriceUK; + + public CPICapFloorTermPriceSurface cpiCFsurfUK; + + // cleanup + + public SavedSettings backup; + + // setup + public CommonVars() + { + backup = new SavedSettings(); + nominalUK = new RelinkableHandle(); + cpiUK = new RelinkableHandle(); + hcpi = new RelinkableHandle(); + zciisD = new List(); + zciisR = new List(); + hii = new RelinkableHandle(); + + nominals = new InitializedList(1,1000000); + // option variables + frequency = Frequency.Annual; + // usual setup + volatility = 0.01; + length = 7; + calendar = new UnitedKingdom(); + convention = BusinessDayConvention.ModifiedFollowing; + Date today = new Date(1, Month.June, 2010); + evaluationDate = calendar.adjust(today); + Settings.setEvaluationDate(evaluationDate); + settlementDays = 0; + fixingDays = 0; + settlement = calendar.advance(today,settlementDays,TimeUnit.Days); + startDate = settlement; + dcZCIIS = new ActualActual(); + dcNominal = new ActualActual(); + + // uk rpi index + // fixing data + Date from = new Date(1, Month.July, 2007); + Date to = new Date(1, Month.June, 2010); + Schedule rpiSchedule = new MakeSchedule().from(from).to(to) + .withTenor(new Period(1,TimeUnit.Months)) + .withCalendar(new UnitedKingdom()) + .withConvention(BusinessDayConvention.ModifiedFollowing).value(); + double[] fixData = { + 206.1, 207.3, 208.0, 208.9, 209.7, 210.9, + 209.8, 211.4, 212.1, 214.0, 215.1, 216.8, // 2008 + 216.5, 217.2, 218.4, 217.7, 216.0, 212.9, + 210.1, 211.4, 211.3, 211.5, 212.8, 213.4, // 2009 + 213.4, 214.4, 215.3, 216.0, 216.6, 218.0, + 217.9, 219.2, 220.7, 222.8, -999, -999, // 2010 + -999}; + + // link from cpi index to cpi TS + bool interp = false;// this MUST be false because the observation lag is only 2 months + // for ZCIIS; but not for contract if the contract uses a bigger lag. + ii = new UKRPI(interp, hcpi); + for (int i=0; i nomD = new List(); + List nomR = new List(); + for (int i = 0; i < nominalDataLength; i++) + { + nomD.Add(nominalData[i].date); + nomR.Add(nominalData[i].rate/100.0); + } + YieldTermStructure nominalTS = new InterpolatedZeroCurve(nomD,nomR,dcNominal); + nominalUK.linkTo(nominalTS); + + // now build the zero inflation curve + observationLag = new Period(2,TimeUnit.Months); + contractObservationLag = new Period(3,TimeUnit.Months); + contractObservationInterpolation = InterpolationType.Flat; + + Datum[] zciisData = + { + new Datum( new Date(1, Month.June, 2011), 3.087 ), + new Datum( new Date(1, Month.June, 2012), 3.12 ), + new Datum( new Date(1, Month.June, 2013), 3.059 ), + new Datum( new Date(1, Month.June, 2014), 3.11 ), + new Datum( new Date(1, Month.June, 2015), 3.15 ), + new Datum( new Date(1, Month.June, 2016), 3.207 ), + new Datum( new Date(1, Month.June, 2017), 3.253 ), + new Datum( new Date(1, Month.June, 2018), 3.288 ), + new Datum( new Date(1, Month.June, 2019), 3.314 ), + new Datum( new Date(1, Month.June, 2020), 3.401 ), + new Datum( new Date(1, Month.June, 2022), 3.458 ), + new Datum( new Date(1, Month.June, 2025), 3.52 ), + new Datum( new Date(1, Month.June, 2030), 3.655 ), + new Datum( new Date(1, Month.June, 2035), 3.668 ), + new Datum( new Date(1, Month.June, 2040), 3.695 ), + new Datum( new Date(1, Month.June, 2050), 3.634 ), + new Datum( new Date(1, Month.June, 2060), 3.629 ), + }; + zciisDataLength = 17; + for (int i = 0; i < zciisDataLength; i++) + { + zciisD.Add(zciisData[i].date); + zciisR.Add(zciisData[i].rate); + } + + // now build the helpers ... + List > helpers = makeHelpers(zciisData, zciisDataLength, ii, + observationLag,calendar, convention, dcZCIIS); + + // we can use historical or first ZCIIS for this + // we know historical is WAY off market-implied, so use market implied flat. + baseZeroRate = zciisData[0].rate/100.0; + PiecewiseZeroInflationCurve pCPIts = new PiecewiseZeroInflationCurve( + evaluationDate, calendar, dcZCIIS, observationLag,ii.frequency(),ii.interpolated(), baseZeroRate, + new Handle(nominalTS), helpers); + pCPIts.recalculate(); + cpiUK.linkTo(pCPIts); + hii.linkTo(ii); + + // make sure that the index has the latest zero inflation term structure + hcpi.linkTo(pCPIts); + + // cpi CF price surf data + Period[] cfMat = {new Period(3,TimeUnit.Years), + new Period(5,TimeUnit.Years), + new Period(7,TimeUnit.Years), + new Period(10,TimeUnit.Years), + new Period(15,TimeUnit.Years), + new Period(20,TimeUnit.Years), + new Period(30,TimeUnit.Years) }; + double[] cStrike = {3, 4, 5, 6}; + double[] fStrike = {-1, 0, 1, 2}; + int ncStrikes = 4, nfStrikes = 4, ncfMaturities = 7; + + double[][] cPrice = + { + new double[4] {227.6, 100.27, 38.8, 14.94}, + new double[4] {345.32, 127.9, 40.59, 14.11}, + new double[4] {477.95, 170.19, 50.62, 16.88}, + new double[4] {757.81, 303.95, 107.62, 43.61}, + new double[4] {1140.73, 481.89, 168.4, 63.65}, + new double[4] {1537.6, 607.72, 172.27, 54.87}, + new double[4] {2211.67, 839.24, 184.75, 45.03} + }; + + double[][] fPrice = + { + new double[4] {15.62, 28.38, 53.61, 104.6}, + new double[4] {21.45, 36.73, 66.66, 129.6}, + new double[4] {24.45, 42.08, 77.04, 152.24}, + new double[4] {39.25, 63.52, 109.2, 203.44}, + new double[4] {36.82, 63.62, 116.97, 232.73}, + new double[4] {39.7, 67.47, 121.79, 238.56}, + new double[4] {41.48, 73.9, 139.75, 286.75} + }; + + // now load the data into vector and Matrix classes + cStrikesUK = new List(); + fStrikesUK = new List(); + cfMaturitiesUK = new List(); + for(int i = 0; i < ncStrikes; i++) cStrikesUK.Add(cStrike[i]); + for(int i = 0; i < nfStrikes; i++) fStrikesUK.Add(fStrike[i]); + for(int i = 0; i < ncfMaturities; i++) cfMaturitiesUK.Add(cfMat[i]); + cPriceUK = new Matrix(ncStrikes, ncfMaturities); + fPriceUK = new Matrix(nfStrikes, ncfMaturities); + for(int i = 0; i < ncStrikes; i++) + { + for(int j = 0; j < ncfMaturities; j++) + { + (cPriceUK)[i,j] = cPrice[j][i]/10000.0; + } + } + for(int i = 0; i < nfStrikes; i++) + { + for(int j = 0; j < ncfMaturities; j++) + { + (fPriceUK)[i,j] = fPrice[j][i]/10000.0; + } + } + } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void cpicapfloorpricesurface() + { + // check inflation leg vs calculation directly from inflation TS + CommonVars common = new CommonVars(); + + double nominal = 1.0; + InterpolatedCPICapFloorTermPriceSurface cpiSurf = new InterpolatedCPICapFloorTermPriceSurface( + nominal, + common.baseZeroRate, + common.observationLag, + common.calendar, + common.convention, + common.dcZCIIS, + common.hii, + common.nominalUK, + common.cStrikesUK, + common.fStrikesUK, + common.cfMaturitiesUK, + common.cPriceUK, + common.fPriceUK); + + // test code - note order of indices + for (int i =0; i "cannot reproduce cpi floor data from surface: " + + a + " vs constructed = " + b); + } + + } + + for (int i =0; i makeYoYLeg(Date startDate, int length,double gearing = 1.0 BusinessDayConvention.Unadjusted,// ref periods & acc periods DateGeneration.Rule.Forward, false); - InitializedList gearingVector = new InitializedList(length, gearing); - InitializedList spreadVector = new InitializedList(length, spread); + List gearingVector = new InitializedList(length, gearing); + List spreadVector = new InitializedList(length, spread); return new yoyInflationLeg(schedule, calendar, ii, observationLag) .withPaymentDayCounter(dc) @@ -192,7 +192,7 @@ public List makeFixedLeg(Date startDate,int length) Schedule schedule = new Schedule(startDate, endDate, new Period(frequency), calendar, convention, convention, DateGeneration.Rule.Forward, false); - InitializedList coupons = new InitializedList(length, 0.0); + List coupons = new InitializedList(length, 0.0); return new FixedRateLeg(schedule) .withCouponRates(coupons, dc) .withNotionals(nominals); @@ -235,8 +235,8 @@ public List makeYoYCapFlooredLeg(int which, Date startDate, } - InitializedList gearingVector = new InitializedList(length, gearing); - InitializedList spreadVector = new InitializedList(length, spread); + List gearingVector = new InitializedList(length, gearing); + List spreadVector = new InitializedList(length, spread); YoYInflationIndex ii = iir as YoYInflationIndex; Date endDate = calendar.advance(startDate,new Period(length,TimeUnit.Years),BusinessDayConvention.Unadjusted); @@ -362,9 +362,9 @@ public void testDecomposition() double error; double floorstrike = 0.05; double capstrike = 0.10; - InitializedList caps = new InitializedList(vars.length,capstrike); + List caps = new InitializedList(vars.length,capstrike); List caps0 = new List(); - InitializedList floors = new InitializedList(vars.length,floorstrike); + List floors = new InitializedList(vars.length,floorstrike); List floors0 = new List(); double gearing_p = 0.5; double spread_p = 0.002; diff --git a/Test/T_LookbackOption.cs b/Test/T_LookbackOption.cs new file mode 100644 index 000000000..510c5224d --- /dev/null +++ b/Test/T_LookbackOption.cs @@ -0,0 +1,497 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ + #if QL_DOTNET_FRAMEWORK + [TestClass()] + #endif + public class T_LookbackOption + { + void REPORT_FAILURE_FLOATING( string greekName, + double minmax, + FloatingTypePayoff payoff, + Exercise exercise, + double s, + double q, + double r, + Date today, + double v, + double expected, + double calculated, + double error, + double tolerance ) + { + QAssert.Fail( exercise.GetType() + " " + + payoff.optionType() + " lookback option with " + + payoff + " payoff:\n" + + " underlying value " + s + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + + void REPORT_FAILURE_FIXED( string greekName, + double minmax, + StrikedTypePayoff payoff, + Exercise exercise, + double s, + double q, + double r, + Date today, + double v, + double expected, + double calculated, + double error, + double tolerance ) + { + QAssert.Fail( exercise.GetType() + " " + + payoff.optionType() + " lookback option with " + + payoff + " payoff:\n" + + " underlying value " + s + "\n" + + " strike: " + payoff.strike() + "\n" + + " dividend yield: " + q + "\n" + + " risk-free rate: " + r + "\n" + + " reference date: " + today + "\n" + + " maturity: " + exercise.lastDate() + "\n" + + " volatility: " + v + "\n\n" + + " expected " + greekName + ": " + expected + "\n" + + " calculated " + greekName + ": " + calculated + "\n" + + " error: " + error + "\n" + + " tolerance: " + tolerance ); + } + + class LookbackOptionData + { + public LookbackOptionData(Option.Type type_,double strike_,double minmax_,double s_,double q_,double r_, + double t_,double v_,double l_,double t1_,double result_,double tol_) + { + type = type_; + strike = strike_; + minmax = minmax_; + s = s_; + q = q_; + r = r_; + t = t_; + v = v_; + l = l_; + t1 = t1_; + result = result_; + tol = tol_; + } + public Option.Type type; + public double strike; + public double minmax; + public double s; // spot + public double q; // dividend + public double r; // risk-free rate + public double t; // time to maturity + public double v; // volatility + + //Partial-time lookback options: + public double l; // level above/below actual extremum + public double t1; // time to start of lookback period + + public double result; // result + public double tol; // tolerance + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testAnalyticContinuousFloatingLookback() + { + // Testing analytic continuous floating-strike lookback options + LookbackOptionData[] values = + { + // data from "Option Pricing Formulas", Haug, 1998, pg.61-62 + new LookbackOptionData(Option.Type.Call, 0, 100, 120.0, 0.06, 0.10, 0.50, 0.30, 0, 0, 25.3533, 1.0e-4), + // data from "Connecting discrete and continuous path-dependent options", + // Broadie, Glasserman & Kou, 1999, pg.70-74 + new LookbackOptionData(Option.Type.Call, 0, 100, 100.0, 0.00, 0.05, 1.00, 0.30, 0, 0, 23.7884, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 100, 100.0, 0.00, 0.05, 0.20, 0.30, 0, 0, 10.7190, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 100, 110.0, 0.00, 0.05, 0.20, 0.30, 0, 0, 14.4597, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 100, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 15.3526, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 16.8468, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 120, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 21.0645, 1.0e-4), + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new AnalyticContinuousFloatingLookbackEngine(stochProcess); + + ContinuousFloatingLookbackOption option = new ContinuousFloatingLookbackOption(values[i].minmax, + payoff, + exercise); + option.setPricingEngine(engine); + + double calculated = option.NPV(); + double expected = values[i].result; + double error = Math.Abs(calculated-expected); + if (error>values[i].tol) { + REPORT_FAILURE_FLOATING("value", values[i].minmax, payoff, + exercise, values[i].s, values[i].q, + values[i].r, today, values[i].v, + expected, calculated, error, + values[i].tol); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testAnalyticContinuousFixedLookback() + { + // Testing analytic continuous fixed-strike lookback options + LookbackOptionData[] values = + { + // data from "Option Pricing Formulas", Haug, 1998, pg.63-64 + //type, strike, minmax, s, q, r, t, v, l, t1, result, tol + new LookbackOptionData( Option.Type.Call, 95, 100, 100.0, 0.00, 0.10, 0.50, 0.10, 0, 0, 13.2687, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 95, 100, 100.0, 0.00, 0.10, 0.50, 0.20, 0, 0, 18.9263, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 95, 100, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 24.9857, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 100, 100, 100.0, 0.00, 0.10, 0.50, 0.10, 0, 0, 8.5126, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 100, 100, 100.0, 0.00, 0.10, 0.50, 0.20, 0, 0, 14.1702, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 100, 100, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 20.2296, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 105, 100, 100.0, 0.00, 0.10, 0.50, 0.10, 0, 0, 4.3908, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 105, 100, 100.0, 0.00, 0.10, 0.50, 0.20, 0, 0, 9.8905, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 105, 100, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 15.8512, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 95, 100, 100.0, 0.00, 0.10, 1.00, 0.10, 0, 0, 18.3241, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 95, 100, 100.0, 0.00, 0.10, 1.00, 0.20, 0, 0, 26.0731, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 95, 100, 100.0, 0.00, 0.10, 1.00, 0.30, 0, 0, 34.7116, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 100, 100, 100.0, 0.00, 0.10, 1.00, 0.10, 0, 0, 13.8000, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 100, 100, 100.0, 0.00, 0.10, 1.00, 0.20, 0, 0, 21.5489, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 100, 100, 100.0, 0.00, 0.10, 1.00, 0.30, 0, 0, 30.1874, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 105, 100, 100.0, 0.00, 0.10, 1.00, 0.10, 0, 0, 9.5445, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 105, 100, 100.0, 0.00, 0.10, 1.00, 0.20, 0, 0, 17.2965, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 105, 100, 100.0, 0.00, 0.10, 1.00, 0.30, 0, 0, 25.9002, 1.0e-4), + + new LookbackOptionData( Option.Type.Put, 95, 100, 100.0, 0.00, 0.10, 0.50, 0.10, 0, 0, 0.6899, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 95, 100, 100.0, 0.00, 0.10, 0.50, 0.20, 0, 0, 4.4448, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 95, 100, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 8.9213, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 100, 100, 100.0, 0.00, 0.10, 0.50, 0.10, 0, 0, 3.3917, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 100, 100, 100.0, 0.00, 0.10, 0.50, 0.20, 0, 0, 8.3177, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 100, 100, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 13.1579, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 105, 100, 100.0, 0.00, 0.10, 0.50, 0.10, 0, 0, 8.1478, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 105, 100, 100.0, 0.00, 0.10, 0.50, 0.20, 0, 0, 13.0739, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 105, 100, 100.0, 0.00, 0.10, 0.50, 0.30, 0, 0, 17.9140, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 95, 100, 100.0, 0.00, 0.10, 1.00, 0.10, 0, 0, 1.0534, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 95, 100, 100.0, 0.00, 0.10, 1.00, 0.20, 0, 0, 6.2813, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 95, 100, 100.0, 0.00, 0.10, 1.00, 0.30, 0, 0, 12.2376, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 100, 100, 100.0, 0.00, 0.10, 1.00, 0.10, 0, 0, 3.8079, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 100, 100, 100.0, 0.00, 0.10, 1.00, 0.20, 0, 0, 10.1294, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 100, 100, 100.0, 0.00, 0.10, 1.00, 0.30, 0, 0, 16.3889, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 105, 100, 100.0, 0.00, 0.10, 1.00, 0.10, 0, 0, 8.3321, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 105, 100, 100.0, 0.00, 0.10, 1.00, 0.20, 0, 0, 14.6536, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 105, 100, 100.0, 0.00, 0.10, 1.00, 0.30, 0, 0, 20.9130, 1.0e-4) + + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new AnalyticContinuousFixedLookbackEngine(stochProcess); + + ContinuousFixedLookbackOption option = new ContinuousFixedLookbackOption(values[i].minmax, + payoff,exercise); + option.setPricingEngine(engine); + + double calculated = option.NPV(); + double expected = values[i].result; + double error = Math.Abs(calculated-expected); + if (error>values[i].tol) + { + REPORT_FAILURE_FIXED("value", values[i].minmax, payoff, exercise, + values[i].s, values[i].q, values[i].r, today, + values[i].v, expected, calculated, error, + values[i].tol); + } + } + + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testAnalyticContinuousPartialFloatingLookback() + { + // Testing analytic continuous partial floating-strike lookback options..."); + LookbackOptionData[] values = + { + // data from "Option Pricing Formulas, Second Edition", Haug, 2006, pg.146 + + //type, strike, minmax, s, q, r, t, v, l, t1, result, tol + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.1, 1, 0.25, 8.6524, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.1, 1, 0.5, 9.2128, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.1, 1, 0.75, 9.5567, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.1, 1, 0.25, 10.5751, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.1, 1, 0.5, 11.2601, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.1, 1, 0.75, 11.6804, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.2, 1, 0.25, 13.3402, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.2, 1, 0.5, 14.5121, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.2, 1, 0.75, 15.314, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.2, 1, 0.25, 16.3047, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.2, 1, 0.5, 17.737, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.2, 1, 0.75, 18.7171, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.3, 1, 0.25, 17.9831, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.3, 1, 0.5, 19.6618, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 90, 90, 0, 0.06, 1, 0.3, 1, 0.75, 20.8493, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.3, 1, 0.25, 21.9793, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.3, 1, 0.5, 24.0311, 1.0e-4), + new LookbackOptionData(Option.Type.Call, 0, 110, 110, 0, 0.06, 1, 0.3, 1, 0.75, 25.4825, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.1, 1, 0.25, 2.7189, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.1, 1, 0.5, 3.4639, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.1, 1, 0.75, 4.1912, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.1, 1, 0.25, 3.3231, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.1, 1, 0.5, 4.2336, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.1, 1, 0.75, 5.1226, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.2, 1, 0.25, 7.9153, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.2, 1, 0.5, 9.5825, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.2, 1, 0.75, 11.0362, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.2, 1, 0.25, 9.6743, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.2, 1, 0.5, 11.7119, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.2, 1, 0.75, 13.4887, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.3, 1, 0.25, 13.4719, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.3, 1, 0.5, 16.1495, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 90, 90, 0, 0.06, 1, 0.3, 1, 0.75, 18.4071, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.3, 1, 0.25, 16.4657, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.3, 1, 0.5, 19.7383, 1.0e-4), + new LookbackOptionData(Option.Type.Put, 0, 110, 110, 0, 0.06, 1, 0.3, 1, 0.75, 22.4976, 1.0e-4) + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new AnalyticContinuousPartialFloatingLookbackEngine(stochProcess); + + Date lookbackEnd = today + Convert.ToInt32(values[i].t1*360+0.5); + ContinuousPartialFloatingLookbackOption option = new ContinuousPartialFloatingLookbackOption( + values[i].minmax,values[i].l,lookbackEnd,payoff,exercise); + option.setPricingEngine(engine); + + double calculated = option.NPV(); + double expected = values[i].result; + double error = Math.Abs(calculated-expected); + if (error>values[i].tol) + { + REPORT_FAILURE_FLOATING("value", values[i].minmax, payoff, + exercise, values[i].s, values[i].q, + values[i].r, today, values[i].v, + expected, calculated, error, + values[i].tol); + } + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testAnalyticContinuousPartialFixedLookback() + { + // Testing analytic continuous fixed-strike lookback options + LookbackOptionData[] values = + { + // data from "Option Pricing Formulas, Second Edition", Haug, 2006, pg.148 + //type, strike, minmax, s, q, r, t, v, l, t1, result, tol + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.1, 0, 0.25, 20.2845, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.1, 0, 0.5, 19.6239, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.1, 0, 0.75, 18.6244, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.1, 0, 0.25, 4.0432, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.1, 0, 0.5, 3.958, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.1, 0, 0.75, 3.7015, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.2, 0, 0.25, 27.5385, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.2, 0, 0.5, 25.8126, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.2, 0, 0.75, 23.4957, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.2, 0, 0.25, 11.4895, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.2, 0, 0.5, 10.8995, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.2, 0, 0.75, 9.8244, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.3, 0, 0.25, 35.4578, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.3, 0, 0.5, 32.7172, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 90, 0, 100, 0, 0.06, 1, 0.3, 0, 0.75, 29.1473, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.3, 0, 0.25, 19.725, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.3, 0, 0.5, 18.4025, 1.0e-4), + new LookbackOptionData( Option.Type.Call, 110, 0, 100, 0, 0.06, 1, 0.3, 0, 0.75, 16.2976, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.1, 0, 0.25, 0.4973, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.1, 0, 0.5, 0.4632, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.1, 0, 0.75, 0.3863, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.1, 0, 0.25, 12.6978, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.1, 0, 0.5, 10.9492, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.1, 0, 0.75, 9.1555, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.2, 0, 0.25, 4.5863, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.2, 0, 0.5, 4.1925, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.2, 0, 0.75, 3.5831, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.2, 0, 0.25, 19.0255, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.2, 0, 0.5, 16.9433, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.2, 0, 0.75, 14.6505, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.3, 0, 0.25, 9.9348, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.3, 0, 0.5, 9.1111, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 90, 0, 100, 0, 0.06, 1, 0.3, 0, 0.75, 7.9267, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.3, 0, 0.25, 25.2112, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.3, 0, 0.5, 22.8217, 1.0e-4), + new LookbackOptionData( Option.Type.Put, 110, 0, 100, 0, 0.06, 1, 0.3, 0, 0.75, 20.0566, 1.0e-4) + }; + + DayCounter dc = new Actual360(); + Date today = Date.Today; + + SimpleQuote spot = new SimpleQuote(0.0); + SimpleQuote qRate = new SimpleQuote(0.0); + YieldTermStructure qTS = Utilities.flatRate(today, qRate, dc); + SimpleQuote rRate = new SimpleQuote(0.0); + YieldTermStructure rTS = Utilities.flatRate(today, rRate, dc); + SimpleQuote vol = new SimpleQuote(0.0); + BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc); + + for (int i=0; i(spot), + new Handle(qTS), + new Handle(rTS), + new Handle(volTS)); + + IPricingEngine engine = new AnalyticContinuousPartialFixedLookbackEngine(stochProcess); + + Date lookbackStart = today + Convert.ToInt32(values[i].t1*360+0.5); + ContinuousPartialFixedLookbackOption option = new ContinuousPartialFixedLookbackOption(lookbackStart, + payoff,exercise); + option.setPricingEngine(engine); + + double calculated = option.NPV(); + double expected = values[i].result; + double error = Math.Abs(calculated-expected); + if (error>values[i].tol) + { + REPORT_FAILURE_FIXED("value", values[i].minmax, payoff, exercise, + values[i].s, values[i].q, values[i].r, today, + values[i].v, expected, calculated, error, + values[i].tol); + } + } + } + } +} diff --git a/Test/T_Matrices.cs b/Test/T_Matrices.cs index 76b92ecb6..83c3da4d4 100644 --- a/Test/T_Matrices.cs +++ b/Test/T_Matrices.cs @@ -28,7 +28,7 @@ under the terms of the QLNet license. You should have received a namespace TestSuite { #if QL_DOTNET_FRAMEWORK - [TestClass()] + [TestClass()] #endif public class T_Matrices { @@ -353,5 +353,39 @@ public void testQRSolve() } } - } +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testInverse() + { + + // Testing LU inverse calculation + setup(); + + double tol = 1.0e-12; + Matrix[] testMatrices = { M1, M2, I, M5 }; + + for (int j = 0; j < testMatrices.Length; j++) + { + Matrix A = testMatrices[j]; + Matrix invA = Matrix.inverse(A); + + Matrix I1 = invA*A; + Matrix I2 = A*invA; + + Matrix eins = new Matrix(A.rows(), A.rows(), 0.0); + for (int i=0; i < A.rows(); ++i) eins[i,i] = 1.0; + + if (norm(I1 - eins) > tol) + Assert.Fail("inverse(A)*A does not recover unit matrix (norm = " + + norm(I1-eins) + ")"); + + if (norm(I2 - eins) > tol) + Assert.Fail("A*inverse(A) does not recover unit matrix (norm = " + + norm(I1-eins) + ")"); + } + } + } } diff --git a/Test/T_SwaptionVolatilityCube.cs b/Test/T_SwaptionVolatilityCube.cs new file mode 100644 index 000000000..b6e4b8548 --- /dev/null +++ b/Test/T_SwaptionVolatilityCube.cs @@ -0,0 +1,424 @@ +// Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.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; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; +#endif +using QLNet; + +namespace TestSuite +{ +#if QL_DOTNET_FRAMEWORK + [TestClass()] +#endif + public class T_SwaptionVolatilityCube + { + public class CommonVars + { + // global data + public SwaptionMarketConventions conventions = new SwaptionMarketConventions(); + public AtmVolatility atm = new AtmVolatility(); + public RelinkableHandle atmVolMatrix; + public VolatilityCube cube = new VolatilityCube(); + public RelinkableHandle termStructure = new RelinkableHandle(); + public SwapIndex swapIndexBase, shortSwapIndexBase; + public bool vegaWeighedSmileFit; + + // cleanup + //public SavedSettings backup = new SavedSettings(); + + // utilities + public void makeAtmVolTest( SwaptionVolatilityCube volCube, double tolerance ) + { + for (int i=0; itolerance) + Assert.Fail("recovery of atm vols failed:" + + "\nexpiry time = " + atm.tenors.options[i] + + "\nswap length = " + atm.tenors.swaps[j] + + "\n atm strike = " + strike + + "\n exp. vol = " + expVol + + "\n actual vol = " + actVol + + "\n error = " + error + + "\n tolerance = " + tolerance); + } + } + } + public void makeVolSpreadsTest(SwaptionVolatilityCube volCube,double tolerance) + { + for (int i=0; itolerance) + Assert.Fail("\nrecovery of smile vol spreads failed:" + + "\n option tenor = " + cube.tenors.options[i] + + "\n swap tenor = " + cube.tenors.swaps[j] + + "\n atm strike = " + atmStrike + + "\n strike spread = " + cube.strikeSpreads[k] + + "\n atm vol = " + atmVol + + "\n smiled vol = " + vol + + "\n vol spread = " + spread + + "\n exp. vol spread = " + expVolSpread + + "\n error = " + error + + "\n tolerance = " + tolerance); + } + } + } + } + + public CommonVars() + { + Settings.setEvaluationDate(new Date(16, Month.September, 2015)); + conventions.setConventions(); + + // ATM swaptionvolmatrix + atm.setMarketData(); + + atmVolMatrix = new RelinkableHandle( + new SwaptionVolatilityMatrix( conventions.calendar,conventions.optionBdc,atm.tenors.options, + atm.tenors.swaps,atm.volsHandle,conventions.dayCounter)); + // Swaptionvolcube + cube.setMarketData(); + + termStructure.linkTo(Utilities.flatRate(0.05, new Actual365Fixed())); + + swapIndexBase = new EuriborSwapIsdaFixA(new Period(2,TimeUnit.Years), termStructure); + shortSwapIndexBase = new EuriborSwapIsdaFixA(new Period(1,TimeUnit.Years), termStructure); + + vegaWeighedSmileFit=false; + } + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testAtmVols() + { + // Testing swaption volatility cube (atm vols) + + CommonVars vars = new CommonVars(); + + SwaptionVolCube2 volCube = new SwaptionVolCube2(vars.atmVolMatrix, + vars.cube.tenors.options, + vars.cube.tenors.swaps, + vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle, + vars.swapIndexBase, + vars.shortSwapIndexBase, + vars.vegaWeighedSmileFit); + + double tolerance = 1.0e-16; + vars.makeAtmVolTest(volCube, tolerance); +} + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testSmile() + { + // Testing swaption volatility cube (smile) + CommonVars vars = new CommonVars(); + + SwaptionVolCube2 volCube = new SwaptionVolCube2(vars.atmVolMatrix, + vars.cube.tenors.options, + vars.cube.tenors.swaps, + vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle, + vars.swapIndexBase, + vars.shortSwapIndexBase, + vars.vegaWeighedSmileFit); + + double tolerance = 1.0e-16; + vars.makeVolSpreadsTest(volCube, tolerance); + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testSabrVols() + { + // Testing swaption volatility cube (sabr interpolation) + CommonVars vars = new CommonVars(); + + List > > parametersGuess = new InitializedList>>( + vars.cube.tenors.options.Count*vars.cube.tenors.swaps.Count); + for (int i=0; i >(4); + parametersGuess[i][0] = new Handle(new SimpleQuote(0.2)); + parametersGuess[i][1] = new Handle(new SimpleQuote(0.5)); + parametersGuess[i][2] = new Handle(new SimpleQuote(0.4)); + parametersGuess[i][3] = new Handle(new SimpleQuote(0.0)); + } + List isParameterFixed = new InitializedList(4, false); + + SwaptionVolCube1x volCube = new SwaptionVolCube1x + ( vars.atmVolMatrix,vars.cube.tenors.options,vars.cube.tenors.swaps,vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle,vars.swapIndexBase,vars.shortSwapIndexBase,vars.vegaWeighedSmileFit, + parametersGuess,isParameterFixed,true); + double tolerance = 3.0e-4; + vars.makeAtmVolTest(volCube, tolerance); + + tolerance = 12.0e-4; + vars.makeVolSpreadsTest(volCube, tolerance); + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testSpreadedCube() + { + + // Testing spreaded swaption volatility cube + CommonVars vars = new CommonVars(); + + List > > parametersGuess = + new InitializedList>>(vars.cube.tenors.options.Count*vars.cube.tenors.swaps.Count); + for (int i=0; i>(4); + parametersGuess[i][0] = new Handle(new SimpleQuote(0.2)); + parametersGuess[i][1] = new Handle(new SimpleQuote(0.5)); + parametersGuess[i][2] = new Handle(new SimpleQuote(0.4)); + parametersGuess[i][3] = new Handle(new SimpleQuote(0.0)); + } + List isParameterFixed = new InitializedList(4, false); + + Handle volCube = new Handle( + new SwaptionVolCube1x(vars.atmVolMatrix, + vars.cube.tenors.options, + vars.cube.tenors.swaps, + vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle, + vars.swapIndexBase, + vars.shortSwapIndexBase, + vars.vegaWeighedSmileFit, + parametersGuess, + isParameterFixed, + true)); + + SimpleQuote spread = new SimpleQuote(0.0001); + Handle spreadHandle = new Handle(spread); + SwaptionVolatilityStructure spreadedVolCube = new SpreadedSwaptionVolatility(volCube, spreadHandle); + List strikes = new List(); + for (int k=1; k<100; k++) + strikes.Add(k*.01); + for (int i=0; i1e-16) + Assert.Fail("\ndiff!=spread in volatility method:" + + "\nexpiry time = " + vars.cube.tenors.options[i] + + "\nswap length = " + vars.cube.tenors.swaps[j] + + "\n atm strike = " + (strike) + + "\ndiff = " + diff + + "\nspread = " + spread.value()); + + diff = smileSectionBySpreadedCube.volatility(strike) - smileSectionByCube.volatility(strike); + if (Math.Abs(diff-spread.value())>1e-16) + Assert.Fail("\ndiff!=spread in smile section method:" + + "\nexpiry time = " + vars.cube.tenors.options[i] + + "\nswap length = " + vars.cube.tenors.swaps[j] + + "\n atm strike = " + (strike) + + "\ndiff = " + diff + + "\nspread = " + spread.value()); + } + } + } + + //testing observability + Flag f = new Flag(); + spreadedVolCube.registerWith(f.update); + volCube.link.update(); + if(!f.isUp()) + Assert.Fail("SpreadedSwaptionVolatilityStructure does not propagate notifications"); + + f.lower(); + spread.setValue(.001); + if(!f.isUp()) + Assert.Fail("SpreadedSwaptionVolatilityStructure does not propagate notifications"); + } + + #if QL_DOTNET_FRAMEWORK + [TestMethod()] + #else + [Fact] + #endif + public void testObservability() + { + // Testing volatility cube observability + CommonVars vars = new CommonVars(); + + List > > parametersGuess = + new InitializedList>>(vars.cube.tenors.options.Count*vars.cube.tenors.swaps.Count); + for (int i=0; i>(4); + parametersGuess[i][0] = new Handle(new SimpleQuote(0.2)); + parametersGuess[i][1] = new Handle(new SimpleQuote(0.5)); + parametersGuess[i][2] = new Handle(new SimpleQuote(0.4)); + parametersGuess[i][3] = new Handle(new SimpleQuote(0.0)); + } + List isParameterFixed = new InitializedList(4, false); + + SwaptionVolCube1x volCube1_0, volCube1_1; + // VolCube created before change of reference date + volCube1_0 = new SwaptionVolCube1x(vars.atmVolMatrix, + vars.cube.tenors.options, + vars.cube.tenors.swaps, + vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle, + vars.swapIndexBase, + vars.shortSwapIndexBase, + vars.vegaWeighedSmileFit, + parametersGuess, + isParameterFixed, + true); + + Date referenceDate = Settings.evaluationDate(); + Settings.setEvaluationDate(vars.conventions.calendar.advance(referenceDate, new Period(1, TimeUnit.Days), + vars.conventions.optionBdc)); + + // VolCube created after change of reference date + volCube1_1 = new SwaptionVolCube1x(vars.atmVolMatrix, + vars.cube.tenors.options, + vars.cube.tenors.swaps, + vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle, + vars.swapIndexBase, + vars.shortSwapIndexBase, + vars.vegaWeighedSmileFit, + parametersGuess, + isParameterFixed, + true); + double dummyStrike = 0.03; + for (int i=0;i 1e-14) + Assert.Fail(" option tenor = " + vars.cube.tenors.options[i] + + " swap tenor = " + vars.cube.tenors.swaps[j] + + " strike = " + (dummyStrike+vars.cube.strikeSpreads[k])+ + " v0 = " + (v0) + + " v1 = " + (v1) + + " error = " + Math.Abs(v1-v0)); + } + } + } + + Settings.setEvaluationDate(referenceDate); + + SwaptionVolCube2 volCube2_0, volCube2_1; + // VolCube created before change of reference date + volCube2_0 = new SwaptionVolCube2(vars.atmVolMatrix, + vars.cube.tenors.options, + vars.cube.tenors.swaps, + vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle, + vars.swapIndexBase, + vars.shortSwapIndexBase, + vars.vegaWeighedSmileFit); + Settings.setEvaluationDate(vars.conventions.calendar.advance(referenceDate, new Period(1, TimeUnit.Days), + vars.conventions.optionBdc)); + + // VolCube created after change of reference date + volCube2_1 = new SwaptionVolCube2(vars.atmVolMatrix, + vars.cube.tenors.options, + vars.cube.tenors.swaps, + vars.cube.strikeSpreads, + vars.cube.volSpreadsHandle, + vars.swapIndexBase, + vars.shortSwapIndexBase, + vars.vegaWeighedSmileFit); + + for (int i=0;i 1e-14) + Assert.Fail(" option tenor = " + vars.cube.tenors.options[i] + + " swap tenor = " + vars.cube.tenors.swaps[j] + + " strike = " + (dummyStrike+vars.cube.strikeSpreads[k])+ + " v0 = " + (v0) + + " v1 = " + (v1) + + " error = " + Math.Abs(v1-v0)); + } + } + } + + Settings.setEvaluationDate(referenceDate); + } + } +} diff --git a/Test/T_TermStructures.cs b/Test/T_TermStructures.cs index 81562b4ca..fe1e8a652 100644 --- a/Test/T_TermStructures.cs +++ b/Test/T_TermStructures.cs @@ -19,7 +19,7 @@ under the terms of the QLNet license. You should have received a */ using System; -using System.Collections.Generic; +using System.Collections.Generic; #if QL_DOTNET_FRAMEWORK using Microsoft.VisualStudio.TestTools.UnitTesting; #else @@ -307,6 +307,92 @@ public void testZSpreadedObs() me.setValue(0.005); if (!flag.isUp()) QAssert.Fail("Observer was not notified of spread change"); - } + } + +#if QL_DOTNET_FRAMEWORK + [TestMethod()] +#else + [Fact] +#endif + public void testInterpolatedZeroCurveWithRefDateAndTenorDates() + { + CommonVars vars = new CommonVars(); + + // Create the interpolated curve + var refDate = new Date( 1, 10, 2015 ); + var dates = new List() + { + new Date(30, 12, 2015), + new Date(30, 3, 2016), + new Date(30, 9, 2016), + new Date(29, 9, 2017), + new Date(28, 9, 2018), + new Date(30, 9, 2019), + new Date(30, 9, 2020), + new Date(30, 9, 2021), + new Date(30, 9, 2022), + new Date(29, 9, 2023), + new Date(30, 9, 2024), + new Date(30, 9, 2025), + new Date(30, 9, 2030), + new Date(28, 9, 2035), + new Date(29, 9, 2045), + }; + + var yields = new List() + { + -0.002558362, + -0.002478462, + -0.00248845, + -0.002498437, + -0.00196903, + -0.001219628, + -0.000209989, + 0.000940221, + 0.00220121, + 0.003493045, + 0.004785712, + 0.00602906, + 0.010909594, + 0.013132837, + 0.01403893 + }; + + var curve = new InterpolatedZeroCurve( dates, + yields, + new ActualActual( ActualActual.Convention.ISMA ), + new Linear(), + Compounding.Continuous, + Frequency.Annual, refDate ); + + Dictionary tenors2 = new Dictionary + { + {new Date(30, 12, 2015), -0.002558362}, + {new Date(30, 3, 2016), -0.002478462}, + {new Date(30, 9, 2016), -0.00248845}, + {new Date(29, 9, 2017), -0.002498437}, + {new Date(28, 9, 2018), -0.00196903}, + {new Date(30, 9, 2019), -0.001219628}, + {new Date(30, 9, 2020), -0.000209989}, + {new Date(30, 9, 2021), 0.000940221}, + {new Date(30, 9, 2022), 0.00220121}, + {new Date(29, 9, 2023), 0.003493045}, + {new Date(30, 9, 2024), 0.004785712}, + {new Date(30, 9, 2025), 0.00602906}, + {new Date(30, 9, 2030), 0.010909594}, + {new Date(28, 9, 2035), 0.013132837}, + {new Date(29, 9, 2045), 0.01403893} + }; + + // Make sure the points come back as expected + var tenors = new[] { 0.25, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 15.0, 20.0, 30.0 }; + + for ( int i = 0; i < tenors.Length; i++ ) + { + var test = curve.interpolation_.value( tenors[i],true ); + Assert.AreEqual( yields[i], test ); + } + Assert.AreNotEqual( yields[0], curve.interpolation_.value( 0.0,true ) ); + } } } diff --git a/Test/Test.csproj b/Test/Test.csproj index 5b1853a65..74ff7ed2c 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -66,12 +66,26 @@ + + + + + + + Code + + + + + + + @@ -116,6 +130,7 @@ + diff --git a/Test/Utilities.cs b/Test/Utilities.cs index 7aa554049..698de93a5 100644 --- a/Test/Utilities.cs +++ b/Test/Utilities.cs @@ -1,5 +1,6 @@ /* Copyright (C) 2008 Siarhei Novik (snovik@gmail.com) + Copyright (C) 2008-2016 Andrea Maggiulli (a.maggiulli@gmail.com) This file is part of QLNet Project https://github.com/amaggiulli/qlnet @@ -19,11 +20,11 @@ under the terms of the QLNet license. You should have received a using System; using System.Collections.Generic; -using System.Linq; -#if QL_DOTNET_FRAMEWORK -using Microsoft.VisualStudio.TestTools.UnitTesting; -#else - using Xunit; +using System.Linq; +#if QL_DOTNET_FRAMEWORK +using Microsoft.VisualStudio.TestTools.UnitTesting; +#else + using Xunit; #endif using QLNet; @@ -105,14 +106,14 @@ public static String exerciseTypeToString(Exercise h) hd = h as EuropeanExercise; if (hd != null) - return "European"; - - hd = h as AmericanExercise; - if ( hd != null ) - return "American"; - - hd = h as BermudanExercise; - if ( hd != null ) + return "European"; + + hd = h as AmericanExercise; + if ( hd != null ) + return "American"; + + hd = h as BermudanExercise; + if ( hd != null ) return "Bermudan"; Utils.QL_FAIL("unknown exercise type"); @@ -120,7 +121,7 @@ public static String exerciseTypeToString(Exercise h) } public static String payoffTypeToString(Payoff h) - { + { object hd = null; hd = h as PlainVanillaPayoff; if (hd != null) @@ -152,81 +153,201 @@ public static String payoffTypeToString(Payoff h) } } - // this cleans up index-fixing histories when disposed + // this cleans up index-fixing histories when disposed public class IndexHistoryCleaner : IDisposable - { + { public void Dispose() { IndexManager.instance().clearHistories(); } }; public static partial class QAssert { public static void Fail(string message) - { + { #if QL_DOTNET_FRAMEWORK Assert.Fail(message); #else Assert.True(false,message); #endif - } - - public static void AreEqual( double expected, double actual, double delta ) - { - #if QL_DOTNET_FRAMEWORK - Assert.AreEqual( expected, actual, delta ); - #else - Assert.True(Math.Abs(expected- actual) <= delta); - #endif } - public static void AreEqual(double expected, double actual, double delta, string message) - { - #if QL_DOTNET_FRAMEWORK - Assert.AreEqual(expected, actual, delta, message); - #else - Assert.True(Math.Abs(expected- actual) <= delta,message); - #endif - } - - public static void AreEqual( T expected, T actual ) - { - - #if QL_DOTNET_FRAMEWORK - Assert.AreEqual( expected, actual ); + public static void AreEqual( double expected, double actual, double delta ) + { + #if QL_DOTNET_FRAMEWORK + Assert.AreEqual( expected, actual, delta ); + #else + Assert.True(Math.Abs(expected- actual) <= delta); + #endif + } + + public static void AreEqual(double expected, double actual, double delta, string message) + { + #if QL_DOTNET_FRAMEWORK + Assert.AreEqual(expected, actual, delta, message); + #else + Assert.True(Math.Abs(expected- actual) <= delta,message); + #endif + } + + public static void AreEqual( T expected, T actual ) + { + + #if QL_DOTNET_FRAMEWORK + Assert.AreEqual( expected, actual ); + #else + Assert.Equal(expected, actual); + #endif + } + + public static void AreEqual( T expected, T actual, string message ) + { + #if QL_DOTNET_FRAMEWORK + Assert.AreEqual( expected, actual, message ); + #else + Assert.Equal(expected, actual); + #endif + } + + public static void IsTrue( bool condition, string message ) + { + #if QL_DOTNET_FRAMEWORK + Assert.IsTrue( condition, message); #else - Assert.Equal(expected, actual); - #endif - } - - public static void AreEqual( T expected, T actual, string message ) - { - #if QL_DOTNET_FRAMEWORK - Assert.AreEqual( expected, actual, message ); + Assert.True( condition, message); + #endif + } + + public static void IsFalse( bool condition, string message ) + { + #if QL_DOTNET_FRAMEWORK + Assert.IsFalse( condition, message ); #else - Assert.Equal(expected, actual); - #endif - } - - public static void IsTrue( bool condition, string message ) - { - #if QL_DOTNET_FRAMEWORK - Assert.IsTrue( condition, message); - #else - Assert.True( condition, message); - #endif - } - - public static void IsFalse( bool condition, string message ) - { - #if QL_DOTNET_FRAMEWORK - Assert.IsFalse( condition, message ); - #else - Assert.False( condition, message); - #endif - } - //public static void IsTrue( bool condition, string message, params object[] parameters ) - //{ - // QAssert.IsTrue( condition, message, parameters ); - //} - + Assert.False( condition, message); + #endif + } + //public static void IsTrue( bool condition, string message, params object[] parameters ) + //{ + // QAssert.IsTrue( condition, message, parameters ); + //} + + } + + public struct SwaptionTenors + { + public List options; + public List swaps; + } + public struct SwaptionMarketConventions + { + public Calendar calendar; + public BusinessDayConvention optionBdc; + public DayCounter dayCounter; + public void setConventions() + { + calendar = new TARGET(); + optionBdc = BusinessDayConvention.ModifiedFollowing; + dayCounter = new Actual365Fixed(); + } + } + + public struct AtmVolatility + { + public SwaptionTenors tenors; + public Matrix vols; + public List > > volsHandle; + public void setMarketData() + { + tenors.options = new InitializedList(6); + tenors.options[0] = new Period(1, TimeUnit.Months); + tenors.options[1] = new Period(6, TimeUnit.Months); + tenors.options[2] = new Period(1, TimeUnit.Years); + tenors.options[3] = new Period(5, TimeUnit.Years); + tenors.options[4] = new Period(10, TimeUnit.Years); + tenors.options[5] = new Period(30, TimeUnit.Years); + tenors.swaps = new InitializedList(4); + tenors.swaps[0] = new Period(1, TimeUnit.Years); + tenors.swaps[1] = new Period(5, TimeUnit.Years); + tenors.swaps[2] = new Period(10, TimeUnit.Years); + tenors.swaps[3] = new Period(30, TimeUnit.Years); + vols = new Matrix(tenors.options.Count, tenors.swaps.Count); + vols[0,0]=0.1300; vols[0,1]=0.1560; vols[0,2]=0.1390; vols[0,3]=0.1220; + vols[1,0]=0.1440; vols[1,1]=0.1580; vols[1,2]=0.1460; vols[1,3]=0.1260; + vols[2,0]=0.1600; vols[2,1]=0.1590; vols[2,2]=0.1470; vols[2,3]=0.1290; + vols[3,0]=0.1640; vols[3,1]=0.1470; vols[3,2]=0.1370; vols[3,3]=0.1220; + vols[4,0]=0.1400; vols[4,1]=0.1300; vols[4,2]=0.1250; vols[4,3]=0.1100; + vols[5,0]=0.1130; vols[5,1]=0.1090; vols[5,2]=0.1070; vols[5,3]=0.0930; + volsHandle= new InitializedList>>(tenors.options.Count); + for (int i=0; i>(tenors.swaps.Count); + for (int j=0; j(new SimpleQuote(vols[i,j])); + } + } + } + + public struct VolatilityCube + { + public SwaptionTenors tenors; + public Matrix volSpreads; + public List > > volSpreadsHandle; + public List strikeSpreads; + public void setMarketData() + { + tenors.options=new InitializedList(3); + tenors.options[0] = new Period(1, TimeUnit.Years); + tenors.options[1] = new Period(10, TimeUnit.Years); + tenors.options[2] = new Period(30, TimeUnit.Years); + tenors.swaps= new InitializedList(3); + tenors.swaps[0] = new Period(2, TimeUnit.Years); + tenors.swaps[1] = new Period(10, TimeUnit.Years); + tenors.swaps[2] = new Period(30, TimeUnit.Years); + strikeSpreads=new InitializedList(5); + strikeSpreads[0] = -0.020; + strikeSpreads[1] = -0.005; + strikeSpreads[2] = +0.000; + strikeSpreads[3] = +0.005; + strikeSpreads[4] = +0.020; + volSpreads = new Matrix(tenors.options.Count*tenors.swaps.Count, strikeSpreads.Count); + volSpreads[0,0] = 0.0599; volSpreads[0,1] = 0.0049; + volSpreads[0,2] = 0.0000; + volSpreads[0,3] =-0.0001; volSpreads[0,4] = 0.0127; + volSpreads[1,0] = 0.0729; volSpreads[1,1] = 0.0086; + volSpreads[1,2] = 0.0000; + volSpreads[1,3] =-0.0024; volSpreads[1,4] = 0.0098; + volSpreads[2,0] = 0.0738; volSpreads[2,1] = 0.0102; + volSpreads[2,2] = 0.0000; + volSpreads[2,3] =-0.0039; volSpreads[2,4] = 0.0065; + volSpreads[3,0] = 0.0465; volSpreads[3,1] = 0.0063; + volSpreads[3,2] = 0.0000; + volSpreads[3,3] =-0.0032; volSpreads[3,4] =-0.0010; + volSpreads[4,0] = 0.0558; volSpreads[4,1] = 0.0084; + volSpreads[4,2] = 0.0000; + volSpreads[4,3] =-0.0050; volSpreads[4,4] =-0.0057; + volSpreads[5,0] = 0.0576; volSpreads[5,1] = 0.0083; + volSpreads[5,2] = 0.0000; + volSpreads[5,3] =-0.0043; volSpreads[5,4] = -0.0014; + volSpreads[6,0] = 0.0437; volSpreads[6,1] = 0.0059; + volSpreads[6,2] = 0.0000; + volSpreads[6,3] =-0.0030; volSpreads[6,4] =-0.0006; + volSpreads[7,0] = 0.0533; volSpreads[7,1] = 0.0078; + volSpreads[7,2] = 0.0000; + volSpreads[7,3] =-0.0045; volSpreads[7,4] =-0.0046; + volSpreads[8,0] = 0.0545; volSpreads[8,1] = 0.0079; + volSpreads[8,2] = 0.0000; + volSpreads[8,3] =-0.0042; volSpreads[8,4] =-0.0020; + volSpreadsHandle = new InitializedList>>(tenors.options.Count*tenors.swaps.Count); + for (int i=0; i>(strikeSpreads.Count); + for (int j=0; j(new SimpleQuote(volSpreads[i,j])); + } + } + } } }