diff --git a/LICENSE.TXT b/LICENSE.TXT index 028efcbf89d..283e1a8a67a 100644 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -127,7 +127,7 @@ Copyright (C) 2016 Nicholas Bertocchi Copyright (C) 2016 Stefano Fondi Copyright (C) 2016, 2017 Fabrice Lecuyer Copyright (C) 2016, 2019, 2020 Eisuke Tani -Copyright (C) 2016, 2022 Quaternion Risk Management Ltd +Copyright (C) 2016, 2020, 2022 Quaternion Risk Management Ltd Copyright (C) 2017 BN Algorithms Ltd Copyright (C) 2017 Joseph Jeisman diff --git a/QuantLib.vcxproj b/QuantLib.vcxproj index d0b34e980ab..d16673a0f06 100644 --- a/QuantLib.vcxproj +++ b/QuantLib.vcxproj @@ -510,6 +510,7 @@ + @@ -1924,6 +1925,7 @@ + diff --git a/QuantLib.vcxproj.filters b/QuantLib.vcxproj.filters index 4e3f928bd69..1c095387389 100644 --- a/QuantLib.vcxproj.filters +++ b/QuantLib.vcxproj.filters @@ -495,6 +495,9 @@ cashflows + + cashflows + cashflows @@ -4529,6 +4532,9 @@ cashflows + + cashflows + cashflows diff --git a/ql/CMakeLists.txt b/ql/CMakeLists.txt index 063ce84db86..31fc37aee31 100644 --- a/ql/CMakeLists.txt +++ b/ql/CMakeLists.txt @@ -1,6 +1,7 @@ set(QL_SOURCES cashflow.cpp cashflows/averagebmacoupon.cpp + cashflows/blackovernightindexedcouponpricer.cpp cashflows/capflooredcoupon.cpp cashflows/capflooredinflationcoupon.cpp cashflows/cashflows.cpp @@ -951,6 +952,7 @@ set(QL_HEADERS auto_link.hpp cashflow.hpp cashflows/averagebmacoupon.hpp + cashflows/blackovernightindexedcouponpricer.hpp cashflows/capflooredcoupon.hpp cashflows/capflooredinflationcoupon.hpp cashflows/cashflows.hpp diff --git a/ql/cashflows/Makefile.am b/ql/cashflows/Makefile.am index cdb593471d2..af7320de996 100644 --- a/ql/cashflows/Makefile.am +++ b/ql/cashflows/Makefile.am @@ -5,6 +5,7 @@ this_includedir=${includedir}/${subdir} this_include_HEADERS = \ all.hpp \ averagebmacoupon.hpp \ + blackovernightindexedcouponpricer.hpp \ capflooredcoupon.hpp \ capflooredinflationcoupon.hpp \ cashflows.hpp \ @@ -42,6 +43,7 @@ this_include_HEADERS = \ cpp_files = \ averagebmacoupon.cpp \ + blackovernightindexedcouponpricer.cpp \ capflooredcoupon.cpp \ capflooredinflationcoupon.cpp \ cashflows.cpp \ diff --git a/ql/cashflows/all.hpp b/ql/cashflows/all.hpp index 6929ae3526e..61cc799efd7 100644 --- a/ql/cashflows/all.hpp +++ b/ql/cashflows/all.hpp @@ -2,6 +2,7 @@ /* Add the files to be included into Makefile.am instead. */ #include +#include #include #include #include diff --git a/ql/cashflows/blackovernightindexedcouponpricer.cpp b/ql/cashflows/blackovernightindexedcouponpricer.cpp new file mode 100644 index 00000000000..1b5569d9997 --- /dev/null +++ b/ql/cashflows/blackovernightindexedcouponpricer.cpp @@ -0,0 +1,521 @@ +/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + Copyright (C) 2020 Quaternion Risk Management Ltd + + 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 + . The license is also available online at + . + + + 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. +*/ +#include + +#include +#include + +namespace QuantLib { + + BlackCompoundingOvernightIndexedCouponPricer::BlackCompoundingOvernightIndexedCouponPricer( + Handle v, + const bool effectiveVolatilityInput) + : CompoundingOvernightIndexedCouponPricer(v, effectiveVolatilityInput) {} + + void BlackCompoundingOvernightIndexedCouponPricer::initialize(const FloatingRateCoupon& coupon) { + OvernightIndexedCouponPricer::initialize(coupon); + + gearing_ = coupon.gearing(); + std::tie(swapletRate_, effectiveSpread_, effectiveIndexFixing_) = CompoundingOvernightIndexedCouponPricer::compute(coupon_->accrualEndDate()); + effectiveCapletVolatility_ = effectiveFloorletVolatility_ = Null(); + } + + Real BlackCompoundingOvernightIndexedCouponPricer::optionletRateGlobal(Option::Type optionType, Real effStrike) const { + Date lastRelevantFixingDate = coupon_->fixingDate(); + if (lastRelevantFixingDate <= Settings::instance().evaluationDate()) { + // the amount is determined + Real a, b; + if (optionType == Option::Call) { + a = effectiveIndexFixing_; + b = effStrike; + } else { + a = effStrike; + b = effectiveIndexFixing_; + } + return gearing_ * std::max(a - b, 0.0); + } else { + // not yet determined, use Black model + QL_REQUIRE(!capletVolatility().empty(), "BlackCompoundingOvernightIndexedCouponPricer: missing optionlet volatility"); + std::vector fixingDates = coupon_->fixingDates(); + QL_REQUIRE(!fixingDates.empty(), "BlackCompoundingOvernightIndexedCouponPricer: empty fixing dates"); + bool shiftedLn = capletVolatility()->volatilityType() == ShiftedLognormal; + Real shift = capletVolatility()->displacement(); + Real stdDev; + Real effectiveTime = capletVolatility()->timeFromReference(fixingDates.back()); + if (effectiveVolatilityInput()) { + // vol input is effective, i.e. we use a plain black model + stdDev = capletVolatility()->volatility(fixingDates.back(), effStrike) * std::sqrt(effectiveTime); + } else { + // vol input is not effective: + // for the standard deviation see Lyashenko, Mercurio, Looking forward to backward looking rates, + // section 6.3. the idea is to dampen the average volatility sigma between the fixing start and fixing end + // date by a linear function going from (fixing start, 1) to (fixing end, 0) + Real fixingStartTime = capletVolatility()->timeFromReference(fixingDates.front()); + Real fixingEndTime = capletVolatility()->timeFromReference(fixingDates.back()); + Real sigma = capletVolatility()->volatility( + std::max(fixingDates.front(), capletVolatility()->referenceDate() + 1), effStrike); + Real T = std::max(fixingStartTime, 0.0); + if (!close_enough(fixingEndTime, T)) + T += std::pow(fixingEndTime - T, 3.0) / std::pow(fixingEndTime - fixingStartTime, 2.0) / 3.0; + stdDev = sigma * std::sqrt(T); + } + if (optionType == Option::Type::Call) + effectiveCapletVolatility_ = stdDev / std::sqrt(effectiveTime); + else + effectiveFloorletVolatility_ = stdDev / std::sqrt(effectiveTime); + Real fixing = shiftedLn ? blackFormula(optionType, effStrike, effectiveIndexFixing_, stdDev, 1.0, shift) + : bachelierBlackFormula(optionType, effStrike, effectiveIndexFixing_, stdDev, 1.0); + return gearing_ * fixing; + } + } + + namespace { + Real cappedFlooredRate(Real r, Option::Type optionType, Real k) { + if (optionType == Option::Call) { + return std::min(r, k); + } else { + return std::max(r, k); + } + } + } // namespace + + Real BlackCompoundingOvernightIndexedCouponPricer::optionletRateLocal(Option::Type optionType, Real effStrike) const { + + QL_REQUIRE(!effectiveVolatilityInput(), + "BlackAverageONIndexedCouponPricer::optionletRateLocal() does not support effective volatility input."); + + // We compute a rate and a rawRate such that + // rate * tau * nominal is the amount of the coupon with daily capped / floored rates + // rawRate * tau * nominal is the amount of the coupon without capping / flooring the rate + // We will then return the difference between rate and rawRate (with the correct sign, see below) + // as the option component of the coupon. + + // See CappedFlooredOvernightIndexedCoupon::effectiveCap(), Floor() for what is passed in as effStrike. + // From this we back out the absolute strike at which the + // - daily rate + spread (spread included) or the + // - daily rate (spread excluded) + // is capped / floored. + + Real absStrike = coupon_->compoundSpreadDaily() ? effStrike + coupon_->spread() : effStrike; + + // This following code is inevitably quite similar to the plain ON coupon pricer code, possibly we can refactor + // this, but as a first step it seems safer to add the full modified code explicitly here and leave the original + // code alone. + + ext::shared_ptr index = ext::dynamic_pointer_cast(coupon_->index()); + + const std::vector& fixingDates = coupon_->fixingDates(); + const std::vector