Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added pricing engines for spread- and basket options #686

Merged
merged 6 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions Python/test/test_basket_option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
"""
Copyright (C) 2024 Klaus Spanderen

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/

QuantLib is free software: you can redistribute it and/or modify it
under the terms of the QuantLib license. You should have received a
copy of the license along with this program; if not, please email
<[email protected]>. The license is also 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.
"""

import unittest

import QuantLib as ql


class BasketOptionTest(unittest.TestCase):
def setUp(self):
self.todaysDate = ql.Date(26, ql.October, 2024)
ql.Settings.instance().evaluationDate = self.todaysDate

def tearDown(self):
ql.Settings.instance().evaluationDate = ql.Date()

def testThreeAssetSpreadOption(self):
"""Testing three asset spread option"""

def build_process(s: float, q: float, v: float) -> ql.BlackScholesMertonProcess:
return ql.BlackScholesMertonProcess(
ql.QuoteHandle(ql.SimpleQuote(s)),
ql.YieldTermStructureHandle(
ql.FlatForward(self.todaysDate, q, ql.Actual365Fixed())
),
ql.YieldTermStructureHandle(
ql.FlatForward(self.todaysDate, 0.05, ql.Actual365Fixed())
),
ql.BlackVolTermStructureHandle(
ql.BlackConstantVol(
self.todaysDate, ql.TARGET(), v, ql.Actual365Fixed()
)
),
)

processes = [
build_process(100, 0.05, 0.3),
build_process(50, 0.07, 0.45),
build_process(50, 0.025, 0.2),
]

processes_vector = ql.GeneralizedBlackScholesProcessVector(processes)

rho = ql.Matrix([[1.0, 0.2, -0.1], [0.2, 1.0, -0.3], [-0.1, -0.3, 1.0]])

exercise = ql.EuropeanExercise(self.todaysDate + ql.Period(1, ql.Years))
payoff = ql.PlainVanillaPayoff(ql.Option.Call, 2.0)

basket_option = ql.BasketOption(
ql.AverageBasketPayoff(payoff, ql.Array([1, -1, -1])), exercise
)

expected = 11.932739641

basket_option.setPricingEngine(ql.ChoiBasketEngine(processes_vector, rho, 10))
self.assertAlmostEqual(basket_option.NPV(), expected)

basket_option.setPricingEngine(ql.DengLiZhouBasketEngine(processes_vector, rho))
self.assertAlmostEqual(basket_option.NPV(), expected, 1)

basket_option.setPricingEngine(
ql.MCEuropeanBasketEngine(
ql.StochasticProcessArray(processes, rho),
"lowdiscrepancy",
timeSteps=1,
requiredTolerance=0.1,
)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 1)

basket_option.setPricingEngine(
ql.FdndimBlackScholesVanillaEngine(
processes_vector, rho, ql.UnsignedIntVector([25, 15, 15]), 15
)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 1)

def testTwoAssetSpreadOption(self):
"""Testing two asset spread option"""

def build_process(s: float, v: float) -> ql.BlackProcess:
return ql.BlackProcess(
ql.QuoteHandle(ql.SimpleQuote(s)),
ql.YieldTermStructureHandle(
ql.FlatForward(self.todaysDate, 0.05, ql.Actual365Fixed())
),
ql.BlackVolTermStructureHandle(
ql.BlackConstantVol(
self.todaysDate, ql.TARGET(), v, ql.Actual365Fixed()
)
),
)

p1 = build_process(100, 0.3)
p2 = build_process(90, 0.45)
rho = -0.75
rho_m = ql.Matrix([[1, rho], [rho, 1]])

processes_vector = ql.GeneralizedBlackScholesProcessVector([p1, p2])

exercise = ql.EuropeanExercise(self.todaysDate + ql.Period(6, ql.Months))
payoff = ql.PlainVanillaPayoff(ql.Option.Put, 10.0)
basket_option = ql.BasketOption(ql.SpreadBasketPayoff(payoff), exercise)

expected = 17.96241322097977

basket_option.setPricingEngine(ql.ChoiBasketEngine(processes_vector, rho_m, 15))
self.assertAlmostEqual(basket_option.NPV(), expected, 10)

basket_option.setPricingEngine(
ql.DengLiZhouBasketEngine(processes_vector, rho_m)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 4)

basket_option.setPricingEngine(ql.KirkEngine(p1, p2, rho))
self.assertAlmostEqual(basket_option.NPV(), expected, 1)

basket_option.setPricingEngine(ql.BjerksundStenslandSpreadEngine(p1, p2, rho))
self.assertAlmostEqual(basket_option.NPV(), expected, 2)

basket_option.setPricingEngine(
ql.OperatorSplittingSpreadEngine(
p1, p2, rho, ql.OperatorSplittingSpreadEngine.First
)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 1)

basket_option.setPricingEngine(
ql.OperatorSplittingSpreadEngine(
p1, p2, rho, ql.OperatorSplittingSpreadEngine.Second
)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 2)

basket_option.setPricingEngine(
ql.FdndimBlackScholesVanillaEngine(
processes_vector, rho_m, ql.UnsignedIntVector([25, 25]), 15
)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 1)

basket_option.setPricingEngine(
ql.Fd2dBlackScholesVanillaEngine(p1, p2, rho, xGrid=25, yGrid=25, tGrid=15)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 1)

basket_option.setPricingEngine(
ql.MCEuropeanBasketEngine(
ql.StochasticProcessArray([p1, p2], rho_m),
"lowdiscrepancy",
timeSteps=1,
requiredTolerance=0.1,
)
)
self.assertAlmostEqual(basket_option.NPV(), expected, 1)


if __name__ == "__main__":
print("testing QuantLib", ql.__version__)
unittest.main(verbosity=2)
111 changes: 100 additions & 11 deletions SWIG/basketoptions.i
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

/*
Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 StatPro Italia srl
Copyright (C) 2005 Dominic Thuillier
Copyright (C) 2007 Joseph Wang
Copyright (C) 2018, 2019 Matthias Lungwitz
Copyright (C) 2024 Klaus Spanderen

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/
Expand Down Expand Up @@ -172,8 +172,8 @@ class MCAmericanBasketEngine : public PricingEngine {
BigInteger seed = 0,
Size nCalibrationSamples = Null<Size>(),
Size polynomOrder = 2,
LsmBasisSystem::PolynomialType polynomType
= LsmBasisSystem::Monomial) {
LsmBasisSystem::PolynomialType polynomType
= LsmBasisSystem::Monomial) {
return new MCAmericanBasketEngine<RNG>(process,
timeSteps,
timeStepsPerYear,
Expand Down Expand Up @@ -233,6 +233,8 @@ class MCAmericanBasketEngine : public PricingEngine {
%{
using QuantLib::StulzEngine;
using QuantLib::KirkEngine;
using QuantLib::BjerksundStenslandSpreadEngine;
using QuantLib::OperatorSplittingSpreadEngine;
using QuantLib::Fd2dBlackScholesVanillaEngine;
%}

Expand All @@ -252,17 +254,104 @@ class KirkEngine : public PricingEngine {
Real correlation);
};

%shared_ptr(BjerksundStenslandSpreadEngine)
class BjerksundStenslandSpreadEngine : public PricingEngine {
public:
BjerksundStenslandSpreadEngine(
const ext::shared_ptr<BlackProcess>& process1,
const ext::shared_ptr<BlackProcess>& process2,
Real correlation);
};

%shared_ptr(OperatorSplittingSpreadEngine)
class OperatorSplittingSpreadEngine : public PricingEngine {
public:
enum Order {First, Second};
OperatorSplittingSpreadEngine(
const ext::shared_ptr<BlackProcess>& process1,
const ext::shared_ptr<BlackProcess>& process2,
Real correlation,
Order order = Second);
};

%shared_ptr(Fd2dBlackScholesVanillaEngine)
class Fd2dBlackScholesVanillaEngine : public PricingEngine {
#if !defined(SWIGJAVA) && !defined(SWIGCSHARP)
%feature("kwargs") Fd2dBlackScholesVanillaEngine;
#endif
public:
Fd2dBlackScholesVanillaEngine(
const ext::shared_ptr<GeneralizedBlackScholesProcess>& p1,
const ext::shared_ptr<GeneralizedBlackScholesProcess>& p2,
Real correlation,
Size xGrid = 100, Size yGrid = 100,
Size tGrid = 50, Size dampingSteps = 0,
const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer(),
bool localVol = false,
Real illegalLocalVolOverwrite = -Null<Real>());
};

%{
using QuantLib::ChoiBasketEngine;
using QuantLib::DengLiZhouBasketEngine;
using QuantLib::FdndimBlackScholesVanillaEngine;
%}

#if defined(SWIGCSHARP)
SWIG_STD_VECTOR_ENHANCED( ext::shared_ptr<GeneralizedBlackScholesProcess> )
#endif
%template(GeneralizedBlackScholesProcessVector)
std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> >;


%shared_ptr(ChoiBasketEngine)
class ChoiBasketEngine : public PricingEngine {
#if !defined(SWIGJAVA) && !defined(SWIGCSHARP)
%feature("kwargs") ChoiBasketEngine;
#endif
public:
Fd2dBlackScholesVanillaEngine(const ext::shared_ptr<GeneralizedBlackScholesProcess>& p1,
const ext::shared_ptr<GeneralizedBlackScholesProcess>& p2,
Real correlation,
Size xGrid = 100, Size yGrid = 100,
Size tGrid = 50, Size dampingSteps = 0,
const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer(),
bool localVol = false,
Real illegalLocalVolOverwrite = -Null<Real>());
ChoiBasketEngine(
std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes,
Matrix rho,
Real lambda = 10.0,
Size maxNrIntegrationSteps = std::numeric_limits<Size>::max(),
bool calcfwdDelta = false,
bool controlVariate = false);
};

%shared_ptr(DengLiZhouBasketEngine)
class DengLiZhouBasketEngine : public PricingEngine {
#if !defined(SWIGJAVA) && !defined(SWIGCSHARP)
%feature("kwargs") DengLiZhouBasketEngine;
#endif
public:
DengLiZhouBasketEngine(
std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes,
Matrix rho);
};

%shared_ptr(FdndimBlackScholesVanillaEngine)
class FdndimBlackScholesVanillaEngine : public PricingEngine {
public:
FdndimBlackScholesVanillaEngine(
std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes,
Matrix rho, Size xGrid, Size tGrid = 50,
Size dampingSteps = 0,
const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer());

%extend {
FdndimBlackScholesVanillaEngine(
std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes,
Matrix rho,
const std::vector<unsigned int>& dim,
Size tGrid = 50,
Size dampingSteps = 0,
const FdmSchemeDesc& schemeDesc = FdmSchemeDesc::Hundsdorfer()) {
return new FdndimBlackScholesVanillaEngine(
processes, rho, to_vector<Size>(dim), tGrid, dampingSteps, schemeDesc
);
}
}
};

%{
Expand Down
10 changes: 10 additions & 0 deletions SWIG/fdm.i
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ using QuantLib::FdmDupire1dOp;
using QuantLib::FdmBlackScholesFwdOp;
using QuantLib::FdmHestonFwdOp;
using QuantLib::FdmSquareRootFwdOp;
using QuantLib::FdmWienerOp;

typedef BoundaryCondition<FdmLinearOp> FdmBoundaryCondition;
typedef std::vector<ext::shared_ptr<FdmBoundaryCondition> > FdmBoundaryConditionSet;
Expand Down Expand Up @@ -885,6 +886,15 @@ class FdmHestonFwdOp : public FdmLinearOpComposite {
= ext::shared_ptr<LocalVolTermStructure>());
};

%shared_ptr(FdmWienerOp)
class FdmWienerOp : public FdmLinearOpComposite {
public:
FdmWienerOp(
ext::shared_ptr<FdmMesher> mesher,
ext::shared_ptr<YieldTermStructure> rTS,
Array lambdas);
};

%{
using QuantLib::TripleBandLinearOp;
using QuantLib::FirstDerivativeOp;
Expand Down
15 changes: 14 additions & 1 deletion SWIG/linearalgebra.i
Original file line number Diff line number Diff line change
Expand Up @@ -604,13 +604,16 @@ class Matrix {
using QuantLib::inverse;
using QuantLib::pseudoSqrt;
using QuantLib::SalvagingAlgorithm;
using QuantLib::CholeskyDecomposition;
using QuantLib::CholeskySolveFor;
using QuantLib::SymmetricSchurDecomposition;
%}

struct SalvagingAlgorithm {
#if defined(SWIGPYTHON)
%rename(NoAlgorithm) None;
#endif
enum Type { None, Spectral };
enum Type {None, Spectral, Hypersphere, LowerDiagonal, Higham, Principal};
};

Matrix inverse(const Matrix& m);
Expand All @@ -627,6 +630,16 @@ class SVD {
const Array& singularValues() const;
};

Matrix CholeskyDecomposition(const Matrix& m, bool flexible = false);
Array CholeskySolveFor(const Matrix& L, const Array& b);

class SymmetricSchurDecomposition {
public:
SymmetricSchurDecomposition(const Matrix &s);
const Array& eigenvalues() const;
const Matrix& eigenvectors() const;
};

%{
using QuantLib::BiCGstab;
using QuantLib::GMRES;
Expand Down