From 3b64c6aa5541d2d8bafbbb13f9da1b9f3349292a Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 13 Mar 2025 16:41:03 -0400 Subject: [PATCH 1/6] add HybridHestonHullWhiteProcess --- .../_hybrid_heston_hullwhite_process.pxd | 17 ++++++++++++++ quantlib/processes/api.py | 1 + .../hybrid_heston_hullwhite_process.pxd | 9 ++++++++ .../hybrid_heston_hullwhite_process.pyx | 23 +++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 quantlib/processes/_hybrid_heston_hullwhite_process.pxd create mode 100644 quantlib/processes/hybrid_heston_hullwhite_process.pxd create mode 100644 quantlib/processes/hybrid_heston_hullwhite_process.pyx diff --git a/quantlib/processes/_hybrid_heston_hullwhite_process.pxd b/quantlib/processes/_hybrid_heston_hullwhite_process.pxd new file mode 100644 index 000000000..e3eb11b2a --- /dev/null +++ b/quantlib/processes/_hybrid_heston_hullwhite_process.pxd @@ -0,0 +1,17 @@ +from quantlib.types cimport Real +from quantlib.handle cimport shared_ptr +from quantlib._stochastic_process cimport StochasticProcess +from ._heston_process cimport HestonProcess +from ._hullwhite_process cimport HullWhiteForwardProcess + +cdef extern from 'ql/processes/hybridhestonhullwhiteprocess.hpp' namespace 'QuantLib' nogil: + + cdef cppclass HybridHestonHullWhiteProcess(StochasticProcess): + enum Discretization: + Euler + BSMHullWhite + HybridHestonHullWhiteProcess( + shared_ptr[HestonProcess]& heston_process, + shared_ptr[HullWhiteForwardProcess]& hullWhiteProcess, + Real corrEquityShortRate, + HybridHestonHullWhiteProcess.Discretization discretization) # = BSMHullWhite diff --git a/quantlib/processes/api.py b/quantlib/processes/api.py index 9aacdf016..1f7ba2772 100644 --- a/quantlib/processes/api.py +++ b/quantlib/processes/api.py @@ -2,3 +2,4 @@ from .bates_process import BatesProcess from .heston_process import HestonProcess from .hullwhite_process import HullWhiteProcess +from .hybrid_heston_hullwhite_process import HybridHestonHullWhiteProcess diff --git a/quantlib/processes/hybrid_heston_hullwhite_process.pxd b/quantlib/processes/hybrid_heston_hullwhite_process.pxd new file mode 100644 index 000000000..7cddff684 --- /dev/null +++ b/quantlib/processes/hybrid_heston_hullwhite_process.pxd @@ -0,0 +1,9 @@ +from quantlib.stochastic_process cimport StochasticProcess + +cdef extern from "ql/processes/hybridhestonhullwhiteprocess.hpp" namespace "QuantLib::HybridHestonHullWhiteProcess" nogil: + cpdef enum class Discretization: + Euler + BSMHullWhite + +cdef class HybridHestonHullWhiteProcess(StochasticProcess): + pass diff --git a/quantlib/processes/hybrid_heston_hullwhite_process.pyx b/quantlib/processes/hybrid_heston_hullwhite_process.pyx new file mode 100644 index 000000000..faef55441 --- /dev/null +++ b/quantlib/processes/hybrid_heston_hullwhite_process.pyx @@ -0,0 +1,23 @@ +from quantlib.types cimport Real +from quantlib.handle cimport static_pointer_cast +from .hullwhite_process cimport HullWhiteForwardProcess +from .heston_process cimport HestonProcess +from . cimport _hullwhite_process as _hw +from . cimport _heston_process as _hp +from . cimport _hybrid_heston_hullwhite_process as _hhhwp + +cdef class HybridHestonHullWhiteProcess(StochasticProcess): + Euler = Discretization.Euler + BSMHullWhite = Discretization.BSMHullWhite + + def __init__(self, + HestonProcess heston_process, + HullWhiteForwardProcess hull_white_process, + Real corr_equity_short_rate, + Discretization discretization=Discretization.BSMHullWhite): + self._thisptr.reset( + new _hhhwp.HybridHestonHullWhiteProcess(static_pointer_cast[_hp.HestonProcess](heston_process._thisptr), + static_pointer_cast[_hw.HullWhiteForwardProcess](hull_white_process._thisptr), + corr_equity_short_rate, + discretization) + ) From ee393f01e267ad0ffa4b2374831b58976293d49e Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 13 Mar 2025 16:42:37 -0400 Subject: [PATCH 2/6] expose HullWhiteForwardProcess --- quantlib/processes/api.py | 2 +- quantlib/processes/hullwhite_process.pxd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quantlib/processes/api.py b/quantlib/processes/api.py index 1f7ba2772..30b310c97 100644 --- a/quantlib/processes/api.py +++ b/quantlib/processes/api.py @@ -1,5 +1,5 @@ from .black_scholes_process import BlackScholesMertonProcess from .bates_process import BatesProcess from .heston_process import HestonProcess -from .hullwhite_process import HullWhiteProcess +from .hullwhite_process import HullWhiteProcess, HullWhiteForwardProcess from .hybrid_heston_hullwhite_process import HybridHestonHullWhiteProcess diff --git a/quantlib/processes/hullwhite_process.pxd b/quantlib/processes/hullwhite_process.pxd index 8e7d0d1b7..d8ccd71fa 100644 --- a/quantlib/processes/hullwhite_process.pxd +++ b/quantlib/processes/hullwhite_process.pxd @@ -4,5 +4,5 @@ from .forwardmeasureprocess cimport ForwardMeasureProcess1D cdef class HullWhiteProcess(StochasticProcess1D): pass -cdef class ForwardHullWhiteProcess(ForwardMeasureProcess1D): +cdef class HullWhiteForwardProcess(ForwardMeasureProcess1D): pass From 70409b5b47e85eb3c8c18aee2970a20e3be04833 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 13 Mar 2025 16:44:49 -0400 Subject: [PATCH 3/6] add MCHestonHullWhiteEngine --- quantlib/methods/montecarlo/_mctraits.pxd | 5 +++ quantlib/pricingengines/api.py | 2 +- .../vanilla/_mchestonhullwhiteengine.pxd | 31 +++++++++++++++++++ .../vanilla/mchestonhullwhiteengine.pxd | 4 +++ .../vanilla/mchestonhullwhiteengine.pyx | 31 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 quantlib/methods/montecarlo/_mctraits.pxd create mode 100644 quantlib/pricingengines/vanilla/_mchestonhullwhiteengine.pxd create mode 100644 quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pxd create mode 100644 quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pyx diff --git a/quantlib/methods/montecarlo/_mctraits.pxd b/quantlib/methods/montecarlo/_mctraits.pxd new file mode 100644 index 000000000..0724c4f36 --- /dev/null +++ b/quantlib/methods/montecarlo/_mctraits.pxd @@ -0,0 +1,5 @@ +cdef extern from 'ql/methods/montecarlo/mctraits.hpp' namespace 'QuantLib' nogil: + cdef cppclass MultiVariate: + pass + cdef cppclass SingleVariate: + pass diff --git a/quantlib/pricingengines/api.py b/quantlib/pricingengines/api.py index 2b94ef52f..c88828e4f 100644 --- a/quantlib/pricingengines/api.py +++ b/quantlib/pricingengines/api.py @@ -9,7 +9,7 @@ from .vanilla.vanilla import AnalyticDividendEuropeanEngine from .vanilla.vanilla import FdHestonHullWhiteVanillaEngine from .vanilla.fdblackscholesvanillaengine import FdBlackScholesVanillaEngine - +from .vanilla.mchestonhullwhiteengine import MCHestonHullWhiteEngine from .swaption.jamshidian_swaption_engine import JamshidianSwaptionEngine from .swaption.black_swaption_engine import ( BlackSwaptionEngine, BachelierSwaptionEngine) diff --git a/quantlib/pricingengines/vanilla/_mchestonhullwhiteengine.pxd b/quantlib/pricingengines/vanilla/_mchestonhullwhiteengine.pxd new file mode 100644 index 000000000..dd8de0c3c --- /dev/null +++ b/quantlib/pricingengines/vanilla/_mchestonhullwhiteengine.pxd @@ -0,0 +1,31 @@ +from quantlib.types cimport BigNatural, Real, Size +from libcpp cimport bool +from quantlib.processes._hybrid_heston_hullwhite_process cimport HybridHestonHullWhiteProcess +from quantlib.pricingengines._pricing_engine cimport PricingEngine +from quantlib.handle cimport shared_ptr +from ._mcvanillaengine cimport MCVanillaEngine +from quantlib.methods.montecarlo._mctraits cimport MultiVariate + +cdef extern from 'ql/pricingengines/vanilla/mchestonhullwhiteengine.hpp' namespace 'QuantLib' nogil: + cdef cppclass MCHestonHullWhiteEngine[RNG=*,S=*](MCVanillaEngine[MultiVariate, RNG, S]): + MCHestonHullWhiteEngine(shared_ptr[HybridHestonHullWhiteProcess]& sp, + Size timeSteps, + Size timeStepsPerYear, + bool antitheticVariate, + bool controlVariate, + Size requiredSamples, + Real requiredTolerance, + Size maxSamples, + BigNatural seed) except + + cdef cppclass MakeMCHestonHullWhiteEngine[RNG=*,S=*]: + MakeMCHestonHullWhiteEngine(shared_ptr[HybridHestonHullWhiteProcess]) + MakeMCHestonHullWhiteEngine& withSteps(Size steps) + MakeMCHestonHullWhiteEngine& withStepsPerYear(Size steps) + MakeMCHestonHullWhiteEngine& withAntitheticVariate(bool b = true) + MakeMCHestonHullWhiteEngine& withControlVariate(bool b = true) + MakeMCHestonHullWhiteEngine& withSamples(Size samples) + MakeMCHestonHullWhiteEngine& withAbsoluteTolerance(Real tolerance) + MakeMCHestonHullWhiteEngine& withMaxSamples(Size samples) + MakeMCHestonHullWhiteEngine& withSeed(BigNatural seed) + # conversion to pricing engine + shared_ptr[PricingEngine] operator() const diff --git a/quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pxd b/quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pxd new file mode 100644 index 000000000..c7926f0ff --- /dev/null +++ b/quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pxd @@ -0,0 +1,4 @@ +from .mcvanillaengine cimport MCVanillaEngine + +cdef class MCHestonHullWhiteEngine(MCVanillaEngine): + pass diff --git a/quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pyx b/quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pyx new file mode 100644 index 000000000..a20d0cc2b --- /dev/null +++ b/quantlib/pricingengines/vanilla/mchestonhullwhiteengine.pyx @@ -0,0 +1,31 @@ +from quantlib.types cimport BigNatural, Integer, Real, Size +from libcpp cimport bool +from quantlib.handle cimport static_pointer_cast +from quantlib.utilities.null cimport Null +from quantlib.processes.hybrid_heston_hullwhite_process cimport HybridHestonHullWhiteProcess +cimport quantlib.processes._hybrid_heston_hullwhite_process as _hhwp +from quantlib._defines cimport QL_MAX_INTEGER +from . cimport _mchestonhullwhiteengine as _mchhw + +cdef class MCHestonHullWhiteEngine(MCVanillaEngine): + def __init__(self, HybridHestonHullWhiteProcess sp, + Size time_steps=Null[Size](), + Size time_steps_per_year=Null[Size](), + bool antithetic_variate=True, + bool control_variate=True, + Size required_samples=Null[Size](), + Real required_tolerance=Null[Real](), + Size max_samples=QL_MAX_INTEGER, + BigNatural seed=0): + self._thisptr.reset( + new _mchhw.MCHestonHullWhiteEngine( + static_pointer_cast[_hhwp.HybridHestonHullWhiteProcess](sp._thisptr), + time_steps, + time_steps_per_year, + antithetic_variate, + control_variate, + required_samples, + required_tolerance, + max_samples, + seed) + ) From 29f8fa7b8e28bb016f322458e0b26b74812cd952 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 13 Mar 2025 16:45:20 -0400 Subject: [PATCH 4/6] expose error_estimate --- quantlib/_instrument.pxd | 1 + quantlib/instrument.pyx | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/quantlib/_instrument.pxd b/quantlib/_instrument.pxd index 0abe5bc1b..cf94572fe 100644 --- a/quantlib/_instrument.pxd +++ b/quantlib/_instrument.pxd @@ -11,6 +11,7 @@ cdef extern from 'ql/instrument.hpp' namespace 'QuantLib': Instrument() bool isExpired() Real NPV() except + + Real errorEstimate() except + Date& valuationDate() except + void setPricingEngine(shared_ptr[PricingEngine]&) T result[T](const string& tag) diff --git a/quantlib/instrument.pyx b/quantlib/instrument.pyx index 1dbf082b2..43d2a124b 100644 --- a/quantlib/instrument.pyx +++ b/quantlib/instrument.pyx @@ -25,6 +25,11 @@ cdef class Instrument(Observable): def __get__(self): return self._thisptr.get().NPV() + @property + def error_estimate(self) -> Real: + """error estimate on the NPV when available""" + return self._thisptr.get().errorEstimate() + property npv: """ Shortcut to the net_present_value property. """ def __get__(self): From f58719700106fbe97942a991594dee0879246473 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 13 Mar 2025 16:46:26 -0400 Subject: [PATCH 5/6] cleanups --- .../vanilla/_mceuropeanhestonengine.pxd | 9 +++++---- .../vanilla/_mcvanillaengine.pxd | 18 ++++-------------- .../vanilla/mceuropeanhestonengine.pyx | 10 +++++----- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/quantlib/pricingengines/vanilla/_mceuropeanhestonengine.pxd b/quantlib/pricingengines/vanilla/_mceuropeanhestonengine.pxd index 3c2b8147c..a3d063f20 100644 --- a/quantlib/pricingengines/vanilla/_mceuropeanhestonengine.pxd +++ b/quantlib/pricingengines/vanilla/_mceuropeanhestonengine.pxd @@ -1,16 +1,17 @@ -include '../../types.pxi' - +from quantlib.types cimport BigNatural, Real, Size from libcpp cimport bool from quantlib.processes._heston_process cimport HestonProcess from quantlib.pricingengines._pricing_engine cimport PricingEngine from quantlib.handle cimport shared_ptr +from ._mcvanillaengine cimport MCVanillaEngine +from quantlib.methods.montecarlo._mctraits cimport MultiVariate #needed to prevent a Forward declaration error cdef extern from 'ql/exercise.hpp': pass -cdef extern from 'ql/pricingengines/vanilla/mceuropeanhestonengine.hpp' namespace 'QuantLib': - cdef cppclass MCEuropeanHestonEngine[RNG=*,S=*,P=*](PricingEngine): +cdef extern from 'ql/pricingengines/vanilla/mceuropeanhestonengine.hpp' namespace 'QuantLib' nogil: + cdef cppclass MCEuropeanHestonEngine[RNG=*,S=*,P=*](MCVanillaEngine[MultiVariate, RNG, S]): MCEuropeanHestonEngine(shared_ptr[HestonProcess]& sp, Size timeSteps, Size timeStepsPerYear, diff --git a/quantlib/pricingengines/vanilla/_mcvanillaengine.pxd b/quantlib/pricingengines/vanilla/_mcvanillaengine.pxd index 3354508b4..fc253e5d1 100644 --- a/quantlib/pricingengines/vanilla/_mcvanillaengine.pxd +++ b/quantlib/pricingengines/vanilla/_mcvanillaengine.pxd @@ -6,31 +6,21 @@ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. """ - -include '../../types.pxi' - -from libcpp.vector cimport vector -from libcpp.string cimport string +from quantlib.types cimport BigNatural, Real, Size from libcpp cimport bool from quantlib.handle cimport shared_ptr -cimport quantlib.processes._stochastic_process as _sp +cimport quantlib._stochastic_process as _sp from quantlib.pricingengines._pricing_engine cimport PricingEngine -cdef extern from 'ql/math/randomnumbers/rngtraits.hpp' namespace 'QuantLib': +cdef extern from 'ql/math/randomnumbers/rngtraits.hpp' namespace 'QuantLib' nogil: cdef cppclass PseudoRandom: pass cdef cppclass LowDiscrepancy: pass -cdef extern from 'ql/methods/montecarlo/mctraits.hpp' namespace 'QuantLib': - cdef cppclass MultiVariate: - pass - cdef cppclass SingleVariate: - pass - -cdef extern from 'ql/pricingengines/vanilla/mcvanillaengine.hpp' namespace 'QuantLib': +cdef extern from 'ql/pricingengines/vanilla/mcvanillaengine.hpp' namespace 'QuantLib' nogil: cdef cppclass MCVanillaEngine[MC, RNG, S=*, INST=*](PricingEngine): MCVanillaEngine(shared_ptr[_sp.StochasticProcess]& sp, Size timeSteps, diff --git a/quantlib/pricingengines/vanilla/mceuropeanhestonengine.pyx b/quantlib/pricingengines/vanilla/mceuropeanhestonengine.pyx index f52b9abe8..62abd062b 100644 --- a/quantlib/pricingengines/vanilla/mceuropeanhestonengine.pyx +++ b/quantlib/pricingengines/vanilla/mceuropeanhestonengine.pyx @@ -1,18 +1,18 @@ from quantlib.types cimport BigNatural, Integer, Real, Size from libcpp cimport bool -from quantlib.handle cimport shared_ptr, static_pointer_cast +from quantlib._defines cimport QL_MAX_INTEGER +from quantlib.handle cimport static_pointer_cast from quantlib.utilities.null cimport Null from quantlib.processes.heston_process cimport HestonProcess cimport quantlib.processes._heston_process as _hp -from .mcvanillaengine cimport MCVanillaEngine from . cimport _mceuropeanhestonengine as _mceh cdef class MCEuropeanHestonEngine(MCVanillaEngine): - def __init__(self, HestonProcess process, Size time_steps=Null[Integer](), - Size steps_per_year=Null[Integer](), bool antithetic_variate=True, + def __init__(self, HestonProcess process, Size time_steps=Null[Size](), + Size steps_per_year=Null[Size](), bool antithetic_variate=True, Size required_samples=Null[Integer](), Real required_tolerance=Null[Real](), - Size max_samples=Null[Integer](), + Size max_samples=QL_MAX_INTEGER, BigNatural seed=0): self._thisptr.reset( new _mceh.MCEuropeanHestonEngine( From 482fb7bb176aebc5472c85dacd96d0439ee40b36 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 13 Mar 2025 16:47:09 -0400 Subject: [PATCH 6/6] more tests --- test/test_hybridhestonhullwhite_process.py | 76 +++++++++++++++++----- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/test/test_hybridhestonhullwhite_process.py b/test/test_hybridhestonhullwhite_process.py index b1aadb49c..bf1d263bc 100644 --- a/test/test_hybridhestonhullwhite_process.py +++ b/test/test_hybridhestonhullwhite_process.py @@ -1,6 +1,3 @@ -from __future__ import division -from __future__ import print_function - import unittest import numpy as np @@ -15,12 +12,14 @@ from quantlib.instruments.vanillaoption import VanillaOption from quantlib.time.api import (today, Years, Actual365Fixed, - Period, May, Date, + Period, May, Date, Actual360, NullCalendar) from quantlib.processes.api import (BlackScholesMertonProcess, HestonProcess, - HullWhiteProcess) + HullWhiteProcess, + HullWhiteForwardProcess, + HybridHestonHullWhiteProcess) from quantlib.models.equity.heston_model import ( HestonModel) @@ -33,7 +32,8 @@ AnalyticBSMHullWhiteEngine, AnalyticHestonEngine, AnalyticHestonHullWhiteEngine, - FdHestonHullWhiteVanillaEngine) + FdHestonHullWhiteVanillaEngine, + MCHestonHullWhiteEngine) from quantlib.quotes import SimpleQuote @@ -43,6 +43,7 @@ from .utilities import flat_rate +from math import sin, exp class HybridHestonHullWhiteProcessTestCase(unittest.TestCase): @@ -95,8 +96,8 @@ def setUp(self): self.dates = dates def test_bsm_hw(self): - print("Testing European option pricing for a BSM process" + - " with one-factor Hull-White model...") + """Testing European option pricing for a BSM process + with one-factor Hull-White model""" dc = Actual365Fixed() todays_date = today() @@ -247,12 +248,8 @@ def test_compare_bsm_bsmhw_hestonhw(self): self.assertAlmostEqual(npv_bsm, npv_hestonhw, delta=tol) def test_compare_BsmHW_HestonHW(self): - """ - From Quantlib test suite - """ - - print("Comparing European option pricing for a BSM " + - "process with one-factor Hull-White model...") + """Comparing European option pricing for a BSM + process with one-factor Hull-White model""" dc = Actual365Fixed() @@ -355,8 +352,8 @@ def test_zanette(self): # constant yield and div curves dates = [todays_date + Period(i, Years) for i in range(3)] - rates = [0.04 for i in range(3)] - divRates = [0.03 for i in range(3)] + rates = [0.04] * 3 + divRates = [0.03] * 3 r_ts = HandleYieldTermStructure(ZeroCurve(dates, rates, dc)) q_ts = HandleYieldTermStructure(ZeroCurve(dates, divRates, dc)) @@ -420,3 +417,50 @@ def price_cal(rho, tGrid): expected_price = [11.38, ] * 4 + [12.79, ] * 4 + [14.06, ] * 4 np.testing.assert_almost_equal(calc_price, expected_price, 2) + + def test_mc_vanilla_pricing(self): + """Testing Monte-Carlo vanilla option pricing""" + dc = Actual360() + todays_date = today() + settings = Settings() + settings.evaluation_date = todays_date + dates = [todays_date + Period(i, Years) for i in range(41)] + rates = [0.03 + 0.0003 * exp(sin(i / 4.0)) for i in range(41)] + div_rates = [0.02 + 0.0001 * exp(sin(i / 5.0)) for i in range(41)] + maturity = todays_date + Period(20, Years) + + s0 = SimpleQuote(100) + r_ts = HandleYieldTermStructure(ZeroCurve(dates, rates, dc)) + q_ts = HandleYieldTermStructure(ZeroCurve(dates, div_rates, dc)) + vol = SimpleQuote(0.25) + vol_ts = BlackConstantVol(todays_date, NullCalendar(), vol, dc) + bsm_process= BlackScholesMertonProcess(s0, q_ts, r_ts, vol_ts) + heston_process = HestonProcess(r_ts, q_ts, s0, 0.0625, 0.5, 0.0625, 1e-5, 0.3) + hw_process = HullWhiteForwardProcess(r_ts, 0.01, 0.01) + hw_process.forward_measure_time = dc.year_fraction(todays_date, maturity) + + tol = 0.05 + corr = [-0.9, -0.5, 0.0, 0.5, 0.9] + strike = [100.0] + exercise = EuropeanExercise(maturity) + for rho in corr: + for s in strike: + joint_process = HybridHestonHullWhiteProcess(heston_process, hw_process, rho) + payoff = PlainVanillaPayoff(OptionType.Put, s) + option_heston_hw = VanillaOption(payoff, exercise) + engine = MCHestonHullWhiteEngine(joint_process, + time_steps=1, + required_tolerance=tol, + seed=42) + option_heston_hw.set_pricing_engine(engine) + hw_model = HullWhite(r_ts, hw_process.a, hw_process.sigma) + option_BsmHW = VanillaOption(payoff, exercise) + option_BsmHW.set_pricing_engine(AnalyticBSMHullWhiteEngine(rho, bsm_process, hw_model)) + calculated = option_heston_hw.npv + error = option_heston_hw.error_estimate + expected = option_BsmHW.npv + print(abs(calculated - expected), error) + if rho == 0: + self.assertTrue(abs(calculated - expected) < tol) + else: + self.assertTrue(abs(calculated - expected) < 1.2 * error)