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

Fix for TreeSwaptionEngine mispricing #1327

Merged
merged 24 commits into from
Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e577808
Refactored discretizedswap to enable distinction between pre- and pos…
ralfkonrad Mar 13, 2022
6e7f555
Added test case to implement against
ralfkonrad Oct 10, 2021
dab438d
TreeSwaptionEngine becomes templated GenericTreeSwaptionEngine
ralfkonrad Oct 10, 2021
7efa5c0
Added DiscretizedSwaption2
ralfkonrad Oct 10, 2021
9f1cb57
Added prepareSwaptionWithSnappedDates(...)
ralfkonrad Oct 12, 2021
030cdbc
Fixed clang warnings
ralfkonrad Oct 12, 2021
400ec45
Added p*AdjustValuesImpl()
ralfkonrad Oct 12, 2021
f9bc929
Working on DiscretizedSwaption2::reset(...)
ralfkonrad Nov 20, 2021
b8ff108
Removed treeswaptionengine.cpp file from build systems
ralfkonrad Nov 20, 2021
6b25599
Avoid CLion warnings
ralfkonrad Feb 27, 2022
7797c0f
Added copyright
ralfkonrad Feb 27, 2022
8f73f38
Working on discretization
ralfkonrad Mar 10, 2022
d85c3af
Fix for compatibility errors
ralfkonrad Mar 11, 2022
0584156
Working on discretization
ralfkonrad Mar 13, 2022
eb9ead1
The snapping is working
ralfkonrad Mar 13, 2022
cb475f3
Adjusted test case
ralfkonrad Mar 14, 2022
e42bcb9
Replace discretizedswaption by discretizedswaption2
ralfkonrad Mar 14, 2022
fd67ce0
Fixed cached values to pass test cases
ralfkonrad Mar 14, 2022
6de5452
Using DiscretizedAsset::CouponAdjustment
ralfkonrad Mar 15, 2022
f8cf2c6
BOOST_REQUIRE prints 1,000,000 succesfull test message in CLion
ralfkonrad Mar 15, 2022
9905e99
Wordings adjusted
ralfkonrad Mar 15, 2022
dc73d23
Fixed compiler incompatibilities
ralfkonrad Mar 15, 2022
1b6556c
Fixed compiler incompatibilities
ralfkonrad Mar 15, 2022
e741173
Fix typo
lballabio Mar 17, 2022
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
6 changes: 6 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ CheckOptions:
value: 1
- key: modernize-use-noexcept.ReplacementString
value: QL_NOEXCEPT
- key: readability-identifier-length.MinimumParameterNameLength
value: 1
- key: readability-identifier-length.MinimumVariableNameLength
value: 1
- key: readability-identifier-length.MinimumLoopCounterNameLength
value: 1
...
3 changes: 3 additions & 0 deletions ql/discretizedasset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ namespace QuantLib {
virtual std::vector<Time> mandatoryTimes() const = 0;
//@}
protected:
/*! Indicates if a coupon should be adjusted in preAdjustValues() or postAdjustValues(). */
enum class CouponAdjustment { pre, post };

/*! This method checks whether the asset was rolled at the
given time. */
bool isOnTime(Time t) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ namespace QuantLib {
void postAdjustValuesImpl() override;

private:
enum class CouponAdjustment { pre, post };
CallableBond::arguments arguments_;
Time redemptionTime_;
std::vector<Time> couponTimes_;
Expand Down
188 changes: 119 additions & 69 deletions ql/pricingengines/swap/discretizedswap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/*
Copyright (C) 2001, 2002, 2003 Sadruddin Rejeb
Copyright (C) 2004, 2007 StatPro Italia srl
Copyright (C) 2022 Ralf Konrad Eckel

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/
Expand All @@ -20,12 +21,13 @@

#include <ql/pricingengines/swap/discretizedswap.hpp>
#include <ql/settings.hpp>
#include <utility>

namespace QuantLib {
namespace {
inline bool useCouponInPostAdjust(const Time& resetTime,
const Time& payTime,
const bool& includeTodaysCashFlows) {
inline bool isResetTimeInPast(const Time& resetTime,
const Time& payTime,
const bool& includeTodaysCashFlows) {
return (resetTime < 0.0) &&
((payTime > 0.0) || (includeTodaysCashFlows && (payTime == 0.0)));
}
Expand All @@ -34,34 +36,62 @@ namespace QuantLib {
DiscretizedSwap::DiscretizedSwap(const VanillaSwap::arguments& args,
const Date& referenceDate,
const DayCounter& dayCounter)
: arguments_(args) {
: DiscretizedSwap(
args,
referenceDate,
dayCounter,
std::vector<CouponAdjustment>(args.fixedPayDates.size(), CouponAdjustment::pre),
std::vector<CouponAdjustment>(args.floatingPayDates.size(), CouponAdjustment::pre)) {}

DiscretizedSwap::DiscretizedSwap(const VanillaSwap::arguments& args,
const Date& referenceDate,
const DayCounter& dayCounter,
std::vector<CouponAdjustment> fixedCouponAdjustments,
std::vector<CouponAdjustment> floatingCouponAdjustments)
: arguments_(args), fixedCouponAdjustments_(std::move(fixedCouponAdjustments)),
floatingCouponAdjustments_(std::move(floatingCouponAdjustments)) {
QL_REQUIRE(
fixedCouponAdjustments_.size() == arguments_.fixedPayDates.size(),
"The fixed coupon adjustments must have the same size as the number of fixed coupons.");
QL_REQUIRE(floatingCouponAdjustments_.size() == arguments_.floatingPayDates.size(),
"The floating coupon adjustments must have the same size as the number of "
"floating coupons.");

// NOLINTNEXTLINE(readability-implicit-bool-conversion)
includeTodaysCashFlows_ = Settings::instance().includeTodaysCashFlows() &&
*Settings::instance().includeTodaysCashFlows();

fixedResetTimes_.resize(args.fixedResetDates.size());
for (Size i=0; i<fixedResetTimes_.size(); ++i)
fixedResetTimes_[i] =
dayCounter.yearFraction(referenceDate,
args.fixedResetDates[i]);

fixedPayTimes_.resize(args.fixedPayDates.size());
for (Size i=0; i<fixedPayTimes_.size(); ++i)
fixedPayTimes_[i] =
dayCounter.yearFraction(referenceDate,
args.fixedPayDates[i]);

floatingResetTimes_.resize(args.floatingResetDates.size());
for (Size i=0; i<floatingResetTimes_.size(); ++i)
floatingResetTimes_[i] =
dayCounter.yearFraction(referenceDate,
args.floatingResetDates[i]);

floatingPayTimes_.resize(args.floatingPayDates.size());
for (Size i=0; i<floatingPayTimes_.size(); ++i)
floatingPayTimes_[i] =
dayCounter.yearFraction(referenceDate,
args.floatingPayDates[i]);
auto includeTodaysCashFlows = Settings::instance().includeTodaysCashFlows() &&
*Settings::instance().includeTodaysCashFlows();

auto nrOfFixedCoupons = args.fixedResetDates.size();
fixedResetTimes_.resize(nrOfFixedCoupons);
fixedPayTimes_.resize(nrOfFixedCoupons);
fixedResetTimeIsInPast_.resize(nrOfFixedCoupons);
for (Size i = 0; i < nrOfFixedCoupons; ++i) {
auto resetTime = dayCounter.yearFraction(referenceDate, args.fixedResetDates[i]);
auto payTime = dayCounter.yearFraction(referenceDate, args.fixedPayDates[i]);
auto resetIsInPast = isResetTimeInPast(resetTime, payTime, includeTodaysCashFlows);

fixedResetTimes_[i] = resetTime;
fixedPayTimes_[i] = payTime;
fixedResetTimeIsInPast_[i] = resetIsInPast;
if (resetIsInPast)
fixedCouponAdjustments_[i] = CouponAdjustment::post;
}

auto nrOfFloatingCoupons = args.floatingResetDates.size();
floatingResetTimes_.resize(nrOfFloatingCoupons);
floatingPayTimes_.resize(nrOfFloatingCoupons);
floatingResetTimeIsInPast_.resize(nrOfFloatingCoupons);
for (Size i = 0; i < nrOfFloatingCoupons; ++i) {
auto resetTime = dayCounter.yearFraction(referenceDate, args.floatingResetDates[i]);
auto payTime = dayCounter.yearFraction(referenceDate, args.floatingPayDates[i]);
auto resetIsInPast = isResetTimeInPast(resetTime, payTime, includeTodaysCashFlows);

floatingResetTimes_[i] = resetTime;
floatingPayTimes_[i] = payTime;
floatingResetTimeIsInPast_[i] = resetIsInPast;
if (resetIsInPast)
floatingCouponAdjustments_[i] = CouponAdjustment::post;
}
}

void DiscretizedSwap::reset(Size size) {
Expand Down Expand Up @@ -92,67 +122,54 @@ namespace QuantLib {

void DiscretizedSwap::preAdjustValuesImpl() {
// floating payments
for (Size i=0; i<floatingResetTimes_.size(); i++) {
for (Size i = 0; i < floatingResetTimes_.size(); i++) {
Time t = floatingResetTimes_[i];
if (t >= 0.0 && isOnTime(t)) {
DiscretizedDiscountBond bond;
bond.initialize(method(), floatingPayTimes_[i]);
bond.rollback(time_);

Real nominal = arguments_.nominal;
Time T = arguments_.floatingAccrualTimes[i];
Spread spread = arguments_.floatingSpreads[i];
Real accruedSpread = nominal*T*spread;
for (Size j=0; j<values_.size(); j++) {
Real coupon = nominal * (1.0 - bond.values()[j])
+ accruedSpread * bond.values()[j];
if (arguments_.type == Swap::Payer)
values_[j] += coupon;
else
values_[j] -= coupon;
}
if (floatingCouponAdjustments_[i] == CouponAdjustment::pre && t >= 0.0 && isOnTime(t)) {
addFloatingCoupon(i);
}
}
// fixed payments
for (Size i=0; i<fixedResetTimes_.size(); i++) {
for (Size i = 0; i < fixedResetTimes_.size(); i++) {
Time t = fixedResetTimes_[i];
if (t >= 0.0 && isOnTime(t)) {
DiscretizedDiscountBond bond;
bond.initialize(method(), fixedPayTimes_[i]);
bond.rollback(time_);

Real fixedCoupon = arguments_.fixedCoupons[i];
for (Size j=0; j<values_.size(); j++) {
Real coupon = fixedCoupon*bond.values()[j];
if (arguments_.type == Swap::Payer)
values_[j] -= coupon;
else
values_[j] += coupon;
}
if (fixedCouponAdjustments_[i] == CouponAdjustment::pre && t >= 0.0 && isOnTime(t)) {
addFixedCoupon(i);
}
}
}

void DiscretizedSwap::postAdjustValuesImpl() {
// floating payments
for (Size i = 0; i < floatingResetTimes_.size(); i++) {
Time t = floatingResetTimes_[i];
if (floatingCouponAdjustments_[i] == CouponAdjustment::post && t >= 0.0 && isOnTime(t)) {
addFloatingCoupon(i);
}
}
// fixed payments
for (Size i = 0; i < fixedResetTimes_.size(); i++) {
Time t = fixedResetTimes_[i];
if (fixedCouponAdjustments_[i] == CouponAdjustment::post && t >= 0.0 && isOnTime(t)) {
addFixedCoupon(i);
}
}

// fixed coupons whose reset time is in the past won't be managed
// in preAdjustValues()
for (Size i=0; i<fixedPayTimes_.size(); i++) {
for (Size i = 0; i < fixedPayTimes_.size(); i++) {
Time t = fixedPayTimes_[i];
Time reset = fixedResetTimes_[i];
if (useCouponInPostAdjust(reset, t, includeTodaysCashFlows_) && isOnTime(t)) {
if (fixedResetTimeIsInPast_[i] && isOnTime(t)) {
Real fixedCoupon = arguments_.fixedCoupons[i];
if (arguments_.type==Swap::Payer)
if (arguments_.type == Swap::Payer)
values_ -= fixedCoupon;
else
values_ += fixedCoupon;
}
}

// the same applies to floating payments whose rate is already fixed
for (Size i=0; i<floatingPayTimes_.size(); i++) {
for (Size i = 0; i < floatingPayTimes_.size(); i++) {
Time t = floatingPayTimes_[i];
Time reset = floatingResetTimes_[i];
if (useCouponInPostAdjust(reset, t, includeTodaysCashFlows_) && isOnTime(t)) {
if (floatingResetTimeIsInPast_[i] && isOnTime(t)) {
Real currentFloatingCoupon = arguments_.floatingCoupons[i];
QL_REQUIRE(currentFloatingCoupon != Null<Real>(),
"current floating coupon not given");
Expand All @@ -163,4 +180,37 @@ namespace QuantLib {
}
}
}

void DiscretizedSwap::addFixedCoupon(Size i) {
DiscretizedDiscountBond bond;
bond.initialize(method(), fixedPayTimes_[i]);
bond.rollback(time_);

Real fixedCoupon = arguments_.fixedCoupons[i];
for (Size j = 0; j < values_.size(); j++) {
Real coupon = fixedCoupon * bond.values()[j];
if (arguments_.type == Swap::Payer)
values_[j] -= coupon;
else
values_[j] += coupon;
}
}

void DiscretizedSwap::addFloatingCoupon(Size i) {
DiscretizedDiscountBond bond;
bond.initialize(method(), floatingPayTimes_[i]);
bond.rollback(time_);

Real nominal = arguments_.nominal;
Time T = arguments_.floatingAccrualTimes[i];
Spread spread = arguments_.floatingSpreads[i];
Real accruedSpread = nominal * T * spread;
for (Size j = 0; j < values_.size(); j++) {
Real coupon = nominal * (1.0 - bond.values()[j]) + accruedSpread * bond.values()[j];
if (arguments_.type == Swap::Payer)
values_[j] += coupon;
else
values_[j] -= coupon;
}
}
}
20 changes: 16 additions & 4 deletions ql/pricingengines/swap/discretizedswap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/*
Copyright (C) 2001, 2002, 2003 Sadruddin Rejeb
Copyright (C) 2004, 2007 StatPro Italia srl
Copyright (C) 2022 Ralf Konrad Eckel

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/
Expand All @@ -25,16 +26,22 @@
#ifndef quantlib_discretized_swap_hpp
#define quantlib_discretized_swap_hpp

#include <ql/instruments/vanillaswap.hpp>
#include <ql/discretizedasset.hpp>
#include <ql/instruments/vanillaswap.hpp>

namespace QuantLib {

class DiscretizedSwap : public DiscretizedAsset {
public:
DiscretizedSwap(const VanillaSwap::arguments&,
DiscretizedSwap(const VanillaSwap::arguments& args,
const Date& referenceDate,
const DayCounter& dayCounter);

DiscretizedSwap(const VanillaSwap::arguments& args,
const Date& referenceDate,
const DayCounter& dayCounter,
std::vector<CouponAdjustment> fixedCouponAdjustments,
std::vector<CouponAdjustment> floatingCouponAdjustments);
void reset(Size size) override;
std::vector<Time> mandatoryTimes() const override;

Expand All @@ -46,13 +53,18 @@ namespace QuantLib {
VanillaSwap::arguments arguments_;
std::vector<Time> fixedResetTimes_;
std::vector<Time> fixedPayTimes_;
std::vector<CouponAdjustment> fixedCouponAdjustments_;
std::vector<bool> fixedResetTimeIsInPast_;
std::vector<Time> floatingResetTimes_;
std::vector<Time> floatingPayTimes_;
bool includeTodaysCashFlows_;
std::vector<CouponAdjustment> floatingCouponAdjustments_;
std::vector<bool> floatingResetTimeIsInPast_;

void addFixedCoupon(Size i);
void addFloatingCoupon(Size i);
};

}


#endif

Loading