Skip to content

Commit

Permalink
Merge pull request #1327.
Browse files Browse the repository at this point in the history
Fix for TreeSwaptionEngine mispricing
  • Loading branch information
lballabio authored Mar 17, 2022
2 parents 1f70a67 + e741173 commit 92e37f9
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 137 deletions.
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

0 comments on commit 92e37f9

Please sign in to comment.