Skip to content

Commit

Permalink
Merge 46a2222 into 37c98ec
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio committed Dec 23, 2021
2 parents 37c98ec + 46a2222 commit 4f16383
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 23 deletions.
35 changes: 24 additions & 11 deletions ql/cashflows/couponpricer.cpp
Expand Up @@ -119,18 +119,25 @@ namespace QuantLib {

Handle<YieldTermStructure> rateCurve = index_->forwardingTermStructure();

Date paymentDate = coupon_->date();
if (paymentDate > rateCurve->referenceDate())
discount_ = rateCurve->discount(paymentDate);
else
discount_ = 1.0;

spreadLegValue_ = spread_ * accrualPeriod_ * discount_;
if (rateCurve.empty()) {
discount_ = Null<Real>(); // might not be needed, will be checked later
QL_DEPRECATED_DISABLE_WARNING
spreadLegValue_ = Null<Real>();
QL_DEPRECATED_ENABLE_WARNING
} else {
Date paymentDate = coupon_->date();
if (paymentDate > rateCurve->referenceDate())
discount_ = rateCurve->discount(paymentDate);
else
discount_ = 1.0;
QL_DEPRECATED_DISABLE_WARNING
spreadLegValue_ = spread_ * accrualPeriod_ * discount_;
QL_DEPRECATED_ENABLE_WARNING
}

}

Real BlackIborCouponPricer::optionletPrice(Option::Type optionType,
Real effStrike) const {
Real BlackIborCouponPricer::optionletRate(Option::Type optionType, Real effStrike) const {
if (fixingDate_ <= Settings::instance().evaluationDate()) {
// the amount is determined
Real a, b;
Expand All @@ -141,7 +148,7 @@ namespace QuantLib {
a = effStrike;
b = coupon_->indexFixing();
}
return std::max(a - b, 0.0)* accrualPeriod_*discount_;
return std::max(a - b, 0.0);
} else {
// not yet determined, use Black model
QL_REQUIRE(!capletVolatility().empty(),
Expand All @@ -158,10 +165,16 @@ namespace QuantLib {
stdDev, 1.0, shift)
: bachelierBlackFormula(optionType, effStrike,
adjustedFixing(), stdDev, 1.0);
return fixing * accrualPeriod_ * discount_;
return fixing;
}
}

Real BlackIborCouponPricer::optionletPrice(Option::Type optionType,
Real effStrike) const {
QL_REQUIRE(discount_ != Null<Rate>(), "no forecast curve provided");
return optionletRate(optionType, effStrike) * accrualPeriod_ * discount_;
}

Rate BlackIborCouponPricer::adjustedFixing(Rate fixing) const {

if (fixing == Null<Rate>())
Expand Down
32 changes: 21 additions & 11 deletions ql/cashflows/couponpricer.hpp
Expand Up @@ -102,6 +102,8 @@ namespace QuantLib {
bool useIndexedCoupon_;
};

QL_DEPRECATED_DISABLE_WARNING

/*! Black-formula pricer for capped/floored Ibor coupons
References for timing adjustments
Black76 Hull, Options, Futures and other
Expand Down Expand Up @@ -132,19 +134,28 @@ namespace QuantLib {
Rate floorletRate(Rate effectiveFloor) const override;

protected:
Real optionletPrice(Option::Type optionType,
Real effStrike) const;
Real optionletPrice(Option::Type optionType, Real effStrike) const;
Real optionletRate(Option::Type optionType, Real effStrike) const;

virtual Rate adjustedFixing(Rate fixing = Null<Rate>()) const;

Real discount_;

/*! \deprecated don't use this data member. Use spread_ instead
and calculate it on the fly if needed. But you
probably won't.
Deprecated in version 1.25.
*/
QL_DEPRECATED
Real spreadLegValue_;

private:
const TimingAdjustment timingAdjustment_;
const Handle<Quote> correlation_;
};

QL_DEPRECATED_ENABLE_WARNING

//! base pricer for vanilla CMS coupons
class CmsCouponPricer : public FloatingRateCouponPricer {
public:
Expand Down Expand Up @@ -208,33 +219,32 @@ namespace QuantLib {

inline Real BlackIborCouponPricer::swapletPrice() const {
// past or future fixing is managed in InterestRateIndex::fixing()

Real swapletPrice = adjustedFixing() * accrualPeriod_ * discount_;
return gearing_ * swapletPrice + spreadLegValue_;
QL_REQUIRE(discount_ != Null<Rate>(), "no forecast curve provided");
return swapletRate() * accrualPeriod_ * discount_;
}

inline Rate BlackIborCouponPricer::swapletRate() const {
return gearing_ * adjustedFixing() + spread_;
}

inline Real BlackIborCouponPricer::capletPrice(Rate effectiveCap) const {
Real capletPrice = optionletPrice(Option::Call, effectiveCap);
return gearing_ * capletPrice;
QL_REQUIRE(discount_ != Null<Rate>(), "no forecast curve provided");
return capletRate(effectiveCap) * accrualPeriod_ * discount_;
}

inline Rate BlackIborCouponPricer::capletRate(Rate effectiveCap) const {
return capletPrice(effectiveCap) / (accrualPeriod_*discount_);
return gearing_ * optionletRate(Option::Call, effectiveCap);
}

inline
Real BlackIborCouponPricer::floorletPrice(Rate effectiveFloor) const {
Real floorletPrice = optionletPrice(Option::Put, effectiveFloor);
return gearing_ * floorletPrice;
QL_REQUIRE(discount_ != Null<Rate>(), "no forecast curve provided");
return floorletRate(effectiveFloor) * accrualPeriod_ * discount_;
}

inline
Rate BlackIborCouponPricer::floorletRate(Rate effectiveFloor) const {
return floorletPrice(effectiveFloor) / (accrualPeriod_*discount_);
return gearing_ * optionletRate(Option::Put, effectiveFloor);
}

}
Expand Down
35 changes: 34 additions & 1 deletion test-suite/cashflows.cpp
Expand Up @@ -505,7 +505,39 @@ void CashFlowsTest::testPartialScheduleLegConstruction() {
BOOST_CHECK_EQUAL(lastCpnF3->referencePeriodStart(), Date(25, Sep, 2020));
BOOST_CHECK_EQUAL(lastCpnF3->referencePeriodEnd(), Date(30, Sep, 2020));
}


void CashFlowsTest::testFixedIborCouponWithoutForecastCurve() {
BOOST_TEST_MESSAGE("Testing past ibor coupon without forecast curve...");

IndexHistoryCleaner cleaner;

Date today = Settings::instance().evaluationDate();

auto index = ext::make_shared<USDLibor>(6*Months);
auto calendar = index->fixingCalendar();

Date fixingDate = calendar.advance(today, -2, Months);
Rate pastFixing = 0.01;
index->addFixing(fixingDate, pastFixing);

Date startDate = index->valueDate(fixingDate);
Date endDate = index->maturityDate(fixingDate);

IborCoupon coupon(endDate, 100.0, startDate, endDate, index->fixingDays(), index);
coupon.setPricer(ext::make_shared<BlackIborCouponPricer>());

BOOST_CHECK_NO_THROW(coupon.amount());

// the main check is the one above, but let's check for consistency too:
Real amount = coupon.amount();
Real expected = pastFixing * coupon.nominal() * coupon.accrualPeriod();
if (std::fabs(amount - expected) > 1e-8) {
BOOST_ERROR("amount mismatch:"
<< "\n calculated: " << amount
<< "\n expected: " << expected);
}
}

test_suite* CashFlowsTest::suite() {
auto* suite = BOOST_TEST_SUITE("Cash flows tests");
suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testSettings));
Expand All @@ -519,6 +551,7 @@ test_suite* CashFlowsTest::suite() {
suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testIrregularFirstCouponReferenceDatesAtEndOfMonth));
suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testIrregularLastCouponReferenceDatesAtEndOfMonth));
suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testPartialScheduleLegConstruction));
suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testFixedIborCouponWithoutForecastCurve));

return suite;
}
1 change: 1 addition & 0 deletions test-suite/cashflows.hpp
Expand Up @@ -32,6 +32,7 @@ class CashFlowsTest {
static void testIrregularFirstCouponReferenceDatesAtEndOfMonth();
static void testIrregularLastCouponReferenceDatesAtEndOfMonth();
static void testPartialScheduleLegConstruction();
static void testFixedIborCouponWithoutForecastCurve();
static boost::unit_test_framework::test_suite* suite();
};

Expand Down

0 comments on commit 4f16383

Please sign in to comment.