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

Added correct treatment of the spot fx rate settlement in code dealing with cross currency swaps #54

Merged
merged 7 commits into from May 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 25 additions & 4 deletions QuantExt/qle/pricingengines/crossccyswapengine.cpp
Expand Up @@ -27,10 +27,11 @@ namespace QuantExt {
CrossCcySwapEngine::CrossCcySwapEngine(const Currency& ccy1, const Handle<YieldTermStructure>& currency1Discountcurve,
const Currency& ccy2, const Handle<YieldTermStructure>& currency2Discountcurve,
const Handle<Quote>& spotFX, boost::optional<bool> includeSettlementDateFlows,
const Date& settlementDate, const Date& npvDate)
const Date& settlementDate, const Date& npvDate, const Date& spotFXSettleDate)
: ccy1_(ccy1), currency1Discountcurve_(currency1Discountcurve), ccy2_(ccy2),
currency2Discountcurve_(currency2Discountcurve), spotFX_(spotFX),
includeSettlementDateFlows_(includeSettlementDateFlows), settlementDate_(settlementDate), npvDate_(npvDate) {
includeSettlementDateFlows_(includeSettlementDateFlows), settlementDate_(settlementDate), npvDate_(npvDate),
spotFXSettleDate_(spotFXSettleDate) {

registerWith(currency1Discountcurve_);
registerWith(currency2Discountcurve_);
Expand Down Expand Up @@ -68,6 +69,15 @@ void CrossCcySwapEngine::calculate() const {
<< referenceDate << ")");
results_.valuationDate = npvDate_;
}
Date spotFXSettleDate = spotFXSettleDate_;
if (spotFXSettleDate_ == Date()) {
spotFXSettleDate = referenceDate;
} else {
QL_REQUIRE(spotFXSettleDate >= referenceDate, "FX settlement date (" << spotFXSettleDate
<< ") cannot be before discount curve "
"reference date ("
<< referenceDate << ")");
}
results_.value = 0.0;
results_.errorEstimate = Null<Real>();
// - Swap::Results
Expand Down Expand Up @@ -108,8 +118,19 @@ void CrossCcySwapEngine::calculate() const {

// Convert to NPV currency if necessary.
if (arguments_.currencies[legNo] != ccy1_) {
results_.legNPV[legNo] *= spotFX_->value();
results_.legBPS[legNo] *= spotFX_->value();
Real settleFXRate = spotFX_->value();
Real spotFXRate = settleFXRate;
if( spotFXSettleDate != referenceDate ) {
//Use the parity relation between discount factors and fx rates to compute spotFXRate
//Generic formula: fx(T1)/fx(T2) = FwdDF_Quote(T1->T2) / FwdDF_Base(T1->T2), where fx represents the currency ratio Base/Quote
Real ccy1DF = currency1Discountcurve_->discount(spotFXSettleDate);
Real ccy2DF = currency2Discountcurve_->discount(spotFXSettleDate);
QL_REQUIRE(ccy2DF != 0.0, "Discount Factor associated with currency " << ccy2_
<< " at maturity " << spotFXSettleDate << " cannot be zero");
spotFXRate *= ccy1DF / ccy2DF;
}
results_.legNPV[legNo] *= spotFXRate;
results_.legBPS[legNo] *= spotFXRate;
}

// Get start date and end date discount for the leg
Expand Down
4 changes: 3 additions & 1 deletion QuantExt/qle/pricingengines/crossccyswapengine.hpp
Expand Up @@ -68,7 +68,8 @@ class CrossCcySwapEngine : public CrossCcySwap::engine {
CrossCcySwapEngine(const Currency& ccy1, const Handle<YieldTermStructure>& currency1DiscountCurve,
const Currency& ccy2, const Handle<YieldTermStructure>& currency2DiscountCurve,
const Handle<Quote>& spotFX, boost::optional<bool> includeSettlementDateFlows = boost::none,
const Date& settlementDate = Date(), const Date& npvDate = Date());
const Date& settlementDate = Date(), const Date& npvDate = Date(),
const Date& spotFXSettleDate = Date());
//@}

//! \name PricingEngine interface
Expand Down Expand Up @@ -96,6 +97,7 @@ class CrossCcySwapEngine : public CrossCcySwap::engine {
boost::optional<bool> includeSettlementDateFlows_;
Date settlementDate_;
Date npvDate_;
Date spotFXSettleDate_;
};
} // namespace QuantExt

Expand Down
47 changes: 41 additions & 6 deletions QuantExt/qle/pricingengines/discountingcurrencyswapengine.cpp
Expand Up @@ -31,9 +31,11 @@ namespace QuantExt {
DiscountingCurrencySwapEngine::DiscountingCurrencySwapEngine(
const std::vector<Handle<YieldTermStructure> >& discountCurves, const std::vector<Handle<Quote> >& fxQuotes,
const std::vector<Currency>& currencies, const Currency& npvCurrency,
boost::optional<bool> includeSettlementDateFlows, Date settlementDate, Date npvDate)
boost::optional<bool> includeSettlementDateFlows, Date settlementDate, Date npvDate,
const std::vector<Date>& spotFXSettleDateVec)
: discountCurves_(discountCurves), fxQuotes_(fxQuotes), currencies_(currencies), npvCurrency_(npvCurrency),
includeSettlementDateFlows_(includeSettlementDateFlows), settlementDate_(settlementDate), npvDate_(npvDate) {
includeSettlementDateFlows_(includeSettlementDateFlows), settlementDate_(settlementDate), npvDate_(npvDate),
spotFXSettleDateVec_(spotFXSettleDateVec) {

QL_REQUIRE(discountCurves_.size() == currencies_.size(), "Number of "
"currencies does not match number of discount curves.");
Expand Down Expand Up @@ -63,8 +65,8 @@ Handle<Quote> DiscountingCurrencySwapEngine::fetchFX(Currency ccy) const {
}

void DiscountingCurrencySwapEngine::calculate() const {

for (Size i = 0; i < arguments_.currency.size(); i++) {
Size numCurrencies = arguments_.currency.size();
for (Size i = 0; i < numCurrencies; i++) {
Currency ccy = arguments_.currency[i];
Handle<YieldTermStructure> yts = fetchTS(ccy);
QL_REQUIRE(!yts.empty(), "Discounting term structure is "
Expand Down Expand Up @@ -103,6 +105,27 @@ void DiscountingCurrencySwapEngine::calculate() const {
<< referenceDate << ")");
results_.valuationDate = npvDate_;
}

//check spotFXSettleDateVec_
std::vector<Date> spotFXSettleDateVec = spotFXSettleDateVec_;
if (spotFXSettleDateVec.size() == 0) {
spotFXSettleDateVec.resize(numCurrencies, referenceDate);
} else {
QL_REQUIRE(spotFXSettleDateVec.size() == numCurrencies, "Number of "
"currencies does not match number of FX settlement dates.");
}
for (Size i = 0; i < numCurrencies; ++i) {
if (spotFXSettleDateVec[i] == Date()) {
spotFXSettleDateVec[i] = referenceDate;
} else {
Currency ccy = arguments_.currency[i];
QL_REQUIRE(spotFXSettleDateVec[i] >= referenceDate, "FX settlement date (" << spotFXSettleDateVec[i]
<< ") for currency " << ccy <<
" cannot be before discount curve "
"reference date ("
<< referenceDate << ")");
}
}
results_.value = 0.0;
results_.errorEstimate = Null<Real>();

Expand Down Expand Up @@ -134,9 +157,21 @@ void DiscountingCurrencySwapEngine::calculate() const {

// Converts into base currency and adds.
Handle<Quote> fx = fetchFX(ccy);
results_.legNPV[i] = results_.inCcyLegNPV[i] * fx->value();
Real settleFXRate = fx->value();
Real spotFXRate = settleFXRate;
if( spotFXSettleDateVec[i] != referenceDate ) {
//Use the parity relation between discount factors and fx rates to compute spotFXRate
//Generic formula: fx(T1)/fx(T2) = FwdDF_Quote(T1->T2) / FwdDF_Base(T1->T2), where fx represents the currency ratio Base/Quote
Real npvCcyDF = npvCcyYts->discount(spotFXSettleDateVec[i]);
Real ccyDF = yts->discount(spotFXSettleDateVec[i]);
QL_REQUIRE(ccyDF != 0.0, "Discount Factor associated with currency " << ccy
<< " at maturity " << spotFXSettleDateVec[i] << " cannot be zero");
spotFXRate *= npvCcyDF / ccyDF;
}

results_.legNPV[i] = results_.inCcyLegNPV[i] * spotFXRate;
if (results_.inCcyLegBPS[i] != Null<Real>()) {
results_.legBPS[i] = results_.inCcyLegBPS[i] * fx->value();
results_.legBPS[i] = results_.inCcyLegBPS[i] * spotFXRate;
} else {
results_.legBPS[i] = Null<Real>();
}
Expand Down
Expand Up @@ -52,7 +52,8 @@ class DiscountingCurrencySwapEngine : public CurrencySwap::engine {
const std::vector<Handle<Quote> >& fxQuotes, const std::vector<Currency>& currencies,
const Currency& npvCurrency,
boost::optional<bool> includeSettlementDateFlows = boost::none,
Date settlementDate = Date(), Date npvDate = Date());
Date settlementDate = Date(), Date npvDate = Date(),
const std::vector<Date>& spotFXSettleDateVec = std::vector<Date>() );
void calculate() const;
std::vector<Handle<YieldTermStructure> > discountCurves() { return discountCurves_; }
std::vector<Currency> currencies() { return currencies_; }
Expand All @@ -69,6 +70,7 @@ class DiscountingCurrencySwapEngine : public CurrencySwap::engine {
boost::optional<bool> includeSettlementDateFlows_;
Date settlementDate_;
Date npvDate_;
std::vector<Date> spotFXSettleDateVec_;
};
} // namespace QuantExt

Expand Down
25 changes: 20 additions & 5 deletions QuantExt/qle/termstructures/crossccybasisswaphelper.cpp
Expand Up @@ -37,15 +37,15 @@ CrossCcyBasisSwapHelper::CrossCcyBasisSwapHelper(
const Handle<YieldTermStructure>& flatDiscountCurve, const Handle<YieldTermStructure>& spreadDiscountCurve,
bool eom, bool flatIsDomestic, boost::optional<Period> flatTenor, boost::optional<Period> spreadTenor,
Real spreadOnFlatLeg, Real flatGearing, Real spreadGearing, const Calendar& flatCalendar,
const Calendar& spreadCalendar)
const Calendar& spreadCalendar, const std::vector<Natural>& spotFXSettleDaysVec, const std::vector<Calendar>& spotFXSettleCalendarVec)
: RelativeDateRateHelper(spreadQuote), spotFX_(spotFX), settlementDays_(settlementDays),
settlementCalendar_(settlementCalendar), swapTenor_(swapTenor), rollConvention_(rollConvention),
flatIndex_(flatIndex), spreadIndex_(spreadIndex), flatDiscountCurve_(flatDiscountCurve),
spreadDiscountCurve_(spreadDiscountCurve), eom_(eom), flatIsDomestic_(flatIsDomestic),
flatTenor_(flatTenor ? *flatTenor : flatIndex_->tenor()),
spreadTenor_(spreadTenor ? *spreadTenor : spreadIndex_->tenor()), spreadOnFlatLeg_(spreadOnFlatLeg),
flatGearing_(flatGearing), spreadGearing_(spreadGearing), flatCalendar_(flatCalendar),
spreadCalendar_(spreadCalendar) {
spreadCalendar_(spreadCalendar), spotFXSettleDaysVec_(spotFXSettleDaysVec), spotFXSettleCalendarVec_(spotFXSettleCalendarVec) {

flatLegCurrency_ = flatIndex_->currency();
spreadLegCurrency_ = spreadIndex_->currency();
Expand All @@ -63,6 +63,14 @@ CrossCcyBasisSwapHelper::CrossCcyBasisSwapHelper(
flatCalendar_ = settlementCalendar;
if (spreadCalendar_.empty())
spreadCalendar_ = settlementCalendar;
//check spotFXSettleDaysVec_ and spotFXSettleCalendarVec_
Size numSpotFXSettleDays = spotFXSettleDaysVec_.size();
QL_REQUIRE(numSpotFXSettleDays == spotFXSettleCalendarVec_.size(),
"Array size of spot fx settlement days must equal that of spot fx settlement calendars");
if(numSpotFXSettleDays == 0) {
spotFXSettleDaysVec_.resize(1,0);
spotFXSettleCalendarVec_.resize(1,settlementCalendar);
}

/* Link the curve being bootstrapped to the index if the index has
no projection curve */
Expand Down Expand Up @@ -99,6 +107,13 @@ void CrossCcyBasisSwapHelper::initializeDates() {

Date settlementDate = settlementCalendar_.advance(refDate, settlementDays_, Days);
Date maturityDate = settlementDate + swapTenor_;
//calc spotFXSettleDate
Date spotFXSettleDate = refDate;
Size numSpotFXSettleDays = spotFXSettleDaysVec_.size();//guarranteed to be at least 1
for (Size i = 0; i < numSpotFXSettleDays; i++) {
//Guarranteed here that spotFXSettleDaysVec_ and spotFXSettleCalendarVec_ have the same size
spotFXSettleDate = spotFXSettleCalendarVec_[i].advance(spotFXSettleDate, spotFXSettleDaysVec_[i], Days);
}

Schedule flatLegSchedule = MakeSchedule()
.from(settlementDate)
Expand Down Expand Up @@ -127,15 +142,15 @@ void CrossCcyBasisSwapHelper::initializeDates() {
/* Arbitrarily set the spread leg as the pay leg */
swap_ = boost::shared_ptr<CrossCcyBasisSwap>(new CrossCcyBasisSwap(
spreadLegNominal, spreadLegCurrency_, spreadLegSchedule, spreadIndex_, 0.0, spreadGearing_, flatLegNominal,
flatLegCurrency_, flatLegSchedule, flatIndex_, 0.0, flatGearing_));
flatLegCurrency_, flatLegSchedule, flatIndex_, spreadOnFlatLeg_, flatGearing_));

boost::shared_ptr<PricingEngine> engine;
if (flatIsDomestic_) {
engine.reset(new CrossCcySwapEngine(flatLegCurrency_, flatDiscountRLH_, spreadLegCurrency_, spreadDiscountRLH_,
spotFX_));
spotFX_, boost::none, Date(), Date(), spotFXSettleDate));
} else {
engine.reset(new CrossCcySwapEngine(spreadLegCurrency_, spreadDiscountRLH_, flatLegCurrency_, flatDiscountRLH_,
spotFX_));
spotFX_, boost::none, Date(), Date(), spotFXSettleDate));
}
swap_->setPricingEngine(engine);

Expand Down
6 changes: 5 additions & 1 deletion QuantExt/qle/termstructures/crossccybasisswaphelper.hpp
Expand Up @@ -60,7 +60,9 @@ class CrossCcyBasisSwapHelper : public RelativeDateRateHelper {
bool flatIsDomestic = true, boost::optional<QuantLib::Period> flatTenor = boost::none,
boost::optional<QuantLib::Period> spreadTenor = boost::none, Real spreadOnFlatLeg = 0.0,
Real flatGearing = 1.0, Real spreadGearing = 1.0, const Calendar& flatCalendar = Calendar(),
const Calendar& spreadCalendar = Calendar());
const Calendar& spreadCalendar = Calendar(),
const std::vector<Natural>& spotFXSettleDaysVec = std::vector<Natural>(),
const std::vector<Calendar>& spotFXSettleCalendar = std::vector<Calendar>());
//! \name RateHelper interface
//@{
Real impliedQuote() const;
Expand Down Expand Up @@ -96,6 +98,8 @@ class CrossCcyBasisSwapHelper : public RelativeDateRateHelper {
Real spreadGearing_;
Calendar flatCalendar_;
Calendar spreadCalendar_;
std::vector<Natural> spotFXSettleDaysVec_;
std::vector<Calendar> spotFXSettleCalendarVec_;

Currency flatLegCurrency_;
Currency spreadLegCurrency_;
Expand Down