Skip to content

Commit

Permalink
Merge pull request #1127.
Browse files Browse the repository at this point in the history
added test for local volatility pricing from Heston model.
  • Loading branch information
lballabio committed Jun 28, 2021
2 parents 5010125 + 818d534 commit d196765
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 0 deletions.
5 changes: 5 additions & 0 deletions ql/math/matrixutilities/sparsematrix.hpp
Expand Up @@ -70,6 +70,11 @@ namespace QuantLib {
SparseMatrixReference;

inline Disposable<Array> prod(const SparseMatrix& A, const Array& x) {
QL_REQUIRE(x.size() == A.size2(),
"vectors and sparse matrices with different sizes ("
<< x.size() << ", " << A.size1() << "x" << A.size2() <<
") cannot be multiplied");

Array b(x.size(), 0.0);

for (Size i=0; i < A.filled1()-1; ++i) {
Expand Down
73 changes: 73 additions & 0 deletions test-suite/hestonmodel.cpp
Expand Up @@ -45,6 +45,7 @@
#include <ql/pricingengines/vanilla/mceuropeanhestonengine.hpp>
#include <ql/processes/hestonprocess.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/termstructures/volatility/equityfx/hestonblackvolsurface.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/termstructures/yield/zerocurve.hpp>
#include <ql/time/calendars/nullcalendar.hpp>
Expand Down Expand Up @@ -3220,6 +3221,77 @@ void HestonModelTest::testAsymptoticControlVariate() {
}
}

void HestonModelTest::testLocalVolFromHestonModel() {
BOOST_TEST_MESSAGE("Testing Local Volatility pricing from Heston Model...");

SavedSettings backup;

const auto todaysDate = Date(28, June, 2021);
Settings::instance().evaluationDate() = todaysDate;

const auto dc = Actual365Fixed();

const Handle<YieldTermStructure> rTS(flatRate(0.15, dc));
const Handle<YieldTermStructure> qTS(flatRate(0.05, dc));

const Handle<Quote> s0(ext::make_shared<SimpleQuote>(100.0));

const Real v0 = 0.1;
const Real rho = -0.75;
const Real sigma = 0.8;
const Real kappa = 1.0;
const Real theta = 0.16;

const auto hestonModel = ext::make_shared<HestonModel>(
ext::make_shared<HestonProcess>(
rTS, qTS, s0, v0, kappa, theta, sigma, rho)
);

VanillaOption option(
ext::make_shared<PlainVanillaPayoff>(Option::Call, 120.0),
ext::make_shared<EuropeanExercise>(todaysDate + Period(1, Years))
);

option.setPricingEngine(
ext::make_shared<AnalyticHestonEngine>(
hestonModel,
AnalyticHestonEngine::OptimalCV,
AnalyticHestonEngine::Integration::gaussLaguerre(192)
)
);

const Real expected = option.NPV();

option.setPricingEngine(
ext::make_shared<FdBlackScholesVanillaEngine>(
ext::make_shared<BlackScholesMertonProcess>(
s0, qTS, rTS,
Handle<BlackVolTermStructure>(
ext::make_shared<HestonBlackVolSurface>(
Handle<HestonModel>(hestonModel),
AnalyticHestonEngine::OptimalCV,
AnalyticHestonEngine::Integration::gaussLaguerre(24)
)
)
),
25, 125, 1, FdmSchemeDesc::Douglas(), true, 0.4
)
);

const Real calculated = option.NPV();

const Real tol = 0.005;
const Real diff = std::fabs(calculated - expected);
if (diff > tol) {
BOOST_ERROR("failed to reproduce Heston model values with "
"local volatility pricing"
<< "\n calculated: " << calculated
<< "\n expected : " << expected
<< "\n difference: " << diff
<< "\n tolerance : " << tol);
}
}


test_suite* HestonModelTest::suite(SpeedLevel speed) {
auto* suite = BOOST_TEST_SUITE("Heston model tests");
Expand Down Expand Up @@ -3251,6 +3323,7 @@ test_suite* HestonModelTest::suite(SpeedLevel speed) {
suite->add(QUANTLIB_TEST_CASE(&HestonModelTest::testHestonEngineIntegration));
suite->add(QUANTLIB_TEST_CASE(&HestonModelTest::testOptimalControlVariateChoice));
suite->add(QUANTLIB_TEST_CASE(&HestonModelTest::testAsymptoticControlVariate));
suite->add(QUANTLIB_TEST_CASE(&HestonModelTest::testLocalVolFromHestonModel));

if (speed <= Fast) {
suite->add(QUANTLIB_TEST_CASE(&HestonModelTest::testDifferentIntegrals));
Expand Down
1 change: 1 addition & 0 deletions test-suite/hestonmodel.hpp
Expand Up @@ -60,6 +60,7 @@ class HestonModelTest {
static void testHestonEngineIntegration();
static void testOptimalControlVariateChoice();
static void testAsymptoticControlVariate();
static void testLocalVolFromHestonModel();

static boost::unit_test_framework::test_suite* suite(SpeedLevel);
static boost::unit_test_framework::test_suite* experimental();
Expand Down
82 changes: 82 additions & 0 deletions test-suite/matrices.cpp
Expand Up @@ -33,9 +33,11 @@
#include <ql/math/matrixutilities/qrdecomposition.hpp>
#include <ql/math/matrixutilities/svd.hpp>
#include <ql/math/matrixutilities/symmetricschurdecomposition.hpp>
#include <ql/math/matrixutilities/sparsematrix.hpp>
#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
#include <cmath>
#include <utility>
#include <numeric>

using namespace QuantLib;
using namespace boost::unit_test_framework;
Expand Down Expand Up @@ -718,6 +720,85 @@ void MatricesTest::testInitializers() {
BOOST_CHECK_EQUAL(m2(1, 2), 6.0);
}


namespace {
#if !defined(QL_NO_UBLAS_SUPPORT)
typedef std::pair< std::pair< std::vector<Size>, std::vector<Size> >,
std::vector<Real> > coordinate_tuple;

coordinate_tuple sparseMatrixToCoordinateTuple(const SparseMatrix& m) {
std::vector<Size> row_idx, col_idx;
std::vector<Real> data;
for (auto iter1 = m.begin1(); iter1 != m.end1(); ++iter1)
for (auto iter2 = iter1.begin(); iter2 != iter1.end(); ++iter2) {
row_idx.push_back(iter1.index1());
col_idx.push_back(iter2.index2());
data.push_back(*iter2);
}

return std::make_pair(std::make_pair(row_idx, col_idx), data);
}

#endif
}

void MatricesTest::testSparseMatrixMemory() {

BOOST_TEST_MESSAGE("Testing sparse matrix memory layout...");

#if !defined(QL_NO_UBLAS_SUPPORT)

SparseMatrix m(8, 4);
BOOST_CHECK_EQUAL(m.filled1(), 1);
BOOST_CHECK_EQUAL(m.size1(), 8);
BOOST_CHECK_EQUAL(m.size2(), 4);
BOOST_CHECK_EQUAL(std::distance(m.begin1(), m.end1()), m.size1());

auto coords = sparseMatrixToCoordinateTuple(m);
BOOST_CHECK_EQUAL(coords.first.first.size(), 0);

m(3, 1) = 42;
coords = sparseMatrixToCoordinateTuple(m);
BOOST_CHECK_EQUAL(std::distance(m.begin1(), m.end1()), m.size1());
BOOST_CHECK_EQUAL(coords.first.first.size(), 1);
BOOST_CHECK_EQUAL(coords.first.first[0], 3);
BOOST_CHECK_EQUAL(coords.first.second[0], 1);
BOOST_CHECK_EQUAL(coords.second[0], 42);

m(1, 2) = 6;
coords = sparseMatrixToCoordinateTuple(m);
BOOST_CHECK_EQUAL(coords.first.first.size(), 2);
BOOST_CHECK_EQUAL(coords.first.first[0], 1);
BOOST_CHECK_EQUAL(coords.first.second[0], 2);
BOOST_CHECK_EQUAL(coords.second[0], 6);

Array x{1, 2, 3, 4};
Array y = prod(m, x);
BOOST_CHECK_EQUAL(y, Array({0, 18, 0, 84}));

m(3, 2) = 43;
coords = sparseMatrixToCoordinateTuple(m);
BOOST_CHECK_EQUAL(coords.first.first.size(), 3);
BOOST_CHECK_EQUAL(coords.first.first[2], 3);
BOOST_CHECK_EQUAL(coords.first.second[2], 2);
BOOST_CHECK_EQUAL(coords.second[2], 43);

m(7, 3) = 44;
coords = sparseMatrixToCoordinateTuple(m);
BOOST_CHECK_EQUAL(coords.first.first.size(), 4);
BOOST_CHECK_EQUAL(coords.first.first[3], 7);
BOOST_CHECK_EQUAL(coords.first.second[3], 3);
BOOST_CHECK_EQUAL(coords.second[3], 44);

Size entries(0);
for (auto iter1 = m.begin1(); iter1 != m.end1(); ++iter1)
entries+=std::distance(iter1.begin(), iter1.end());

BOOST_CHECK_EQUAL(entries, 4);

#endif
}

test_suite* MatricesTest::suite() {
auto* suite = BOOST_TEST_SUITE("Matrix tests");

Expand All @@ -731,6 +812,7 @@ test_suite* MatricesTest::suite() {
#if !defined(QL_NO_UBLAS_SUPPORT)
suite->add(QUANTLIB_TEST_CASE(&MatricesTest::testInverse));
suite->add(QUANTLIB_TEST_CASE(&MatricesTest::testDeterminant));
suite->add(QUANTLIB_TEST_CASE(&MatricesTest::testSparseMatrixMemory));
#endif
suite->add(QUANTLIB_TEST_CASE(&MatricesTest::testCholeskyDecomposition));
suite->add(QUANTLIB_TEST_CASE(&MatricesTest::testMoorePenroseInverse));
Expand Down
2 changes: 2 additions & 0 deletions test-suite/matrices.hpp
Expand Up @@ -42,6 +42,8 @@ class MatricesTest {
static void testMoorePenroseInverse();
static void testIterativeSolvers();
static void testInitializers();
static void testSparseMatrixMemory();

static boost::unit_test_framework::test_suite* suite();
};

Expand Down

0 comments on commit d196765

Please sign in to comment.