20#include <ql/cashflows/coupon.hpp>
21#include <ql/math/interpolations/bilinearinterpolation.hpp>
22#include <ql/time/calendars/jointcalendar.hpp>
23#include <ql/time/daycounters/actualactual.hpp>
24#include <ql/timegrid.hpp>
29std::string getDateStr(
const Date& d) {
30 std::ostringstream os;
31 os << QuantLib::io::iso_date(d);
37 const QuantLib::ext::shared_ptr<QuantExt::EquityIndex2>& equity,
38 const QuantLib::ext::shared_ptr<FxIndex>& fxConversion)
39 : today_(today), dc_(dc), N0_(N0), equity_(equity), fxConversion_(fxConversion) {}
106 const std::vector<ConvertibleBond2::CallabilityData>& data)
const {
107 Date result = Date::maxDate();
108 for (
auto const& x : data) {
109 if (x.exerciseDate > d)
110 result = std::min(result, x.exerciseDate);
112 if (result == Date::maxDate())
118 Date result = Date::maxDate();
120 if (x.exerciseDate > d)
121 result = std::min(result, x.exerciseDate);
123 if (result == Date::maxDate())
131 if (QuantLib::ext::dynamic_pointer_cast<Coupon>(c) ==
nullptr)
135 bool isRedemption = QuantLib::ext::dynamic_pointer_cast<Coupon>(d) ==
nullptr;
136 Size index =
grid_.index(
time(d->date()));
147 std::vector<bool>& targetFlags, std::vector<CallData>& targetData) {
148 for (
auto const& c : sourceData) {
155 indexEnd = indexStart;
158 QL_REQUIRE(nextDate != Null<Date>(),
159 "FdConvertibleBondEvents::processExerciseData(): internal error: did not find a next exercise "
162 <<
", the last exercise date should not have exercise type FromThisDateOn");
165 indexEnd =
grid_.index(
time(nextDate)) - 1;
167 QL_FAIL(
"FdConvertibleBondEvents: internal error, exercise type not "
170 for (Size i = indexStart; i <= indexEnd; ++i) {
171 targetFlags[i] =
true;
172 targetData[i] =
CallData{c.
price, c.priceType, c.includeAccrual,
173 c.isSoft, c.softTriggerRatio, std::function<Real(Real, Real)>()};
186 QL_REQUIRE(stockPrices.size() >= 2,
187 "FdConvertibleBondEvents::processMakeWholeData(): at least two stock prices required (cr increase)");
189 effDates.size() >= 2,
190 "FdConvertibleBondEvents::processMakeWholeData(): at least two effective dates required (cr increase)");
191 QL_REQUIRE(effDates.size() == crInc.size(),
"FdConvertibleBondEvents::processMakeWholeData(): effective dates ("
192 << effDates.size() <<
") must match cr increase rows ("
193 << crInc.size() <<
") (cr increase)");
194 for (
auto const& c : crInc) {
195 QL_REQUIRE(c.size() == stockPrices.size(),
196 "FdConvertibleBondEvents::processMakeWholeData(): stock prices size ("
197 << stockPrices.size() <<
") must match cr increase coluns (" << c.size() <<
")");
202 mw_cr_inc_x_ = Array(stockPrices.begin(), stockPrices.end());
206 for (Size i = 0; i < effDates.size(); ++i) {
208 for (Size j = 0; j < stockPrices.size(); ++j) {
213 auto interpolation = QuantLib::ext::make_shared<BilinearInterpolation>(
218 Real cap = QL_MAX_REAL;
224 for (Size i = 0; i <
grid_.size(); ++i) {
227 callData_[i].mwCr = [interpolation, t, cap](
const Real S,
const Real cr) {
228 if ((S < interpolation->xMin() && !
close_enough(S, interpolation->xMin())) ||
229 (S > interpolation->xMax() && !
close_enough(S, interpolation->xMax())) ||
230 (t < interpolation->yMin() && !
close_enough(t, interpolation->yMin())) ||
231 (t > interpolation->yMax() && !
close_enough(t, interpolation->yMax()))) {
234 Real tmp = interpolation->operator()(S, t);
236 return std::max(cr, std::min(cr + tmp, cap));
248 std::set<std::pair<Date, Real>> crSchedule;
250 crSchedule.insert(std::make_pair(d.fromDate, d.conversionRatio));
252 if (!crSchedule.empty()) {
268 Real newCr = Null<Real>();
271 std::map<Date, AdjEvent> adjEvents;
274 adjEvents[d.resetDate].cd = &d;
278 adjEvents[d.protectionDate].dd = &d;
282 adjEvents[d.fromDate].newCr = d.conversionRatio;
286 adjEvents[d.exerciseDate].vd = &d;
292 Size lastDividendProtectionTimeIndex = Null<Size>();
293 bool haveStochasticCr =
false;
295 for (
auto const& event : adjEvents) {
297 if (event.second.cd !=
nullptr) {
312 Real referenceCP =
N0_ / cr;
315 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_referenceCP"] = referenceCP;
318 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_globalFloor"] =
320 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_fxConversion"] = fx;
321 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_currentCr"] = currentCr;
323 Real adjustedConversionRatio = QL_MAX_REAL;
325 adjustedConversionRatio = std::min(adjustedConversionRatio,
N0_ / (c.
gearing * S));
328 adjustedConversionRatio = std::min(adjustedConversionRatio,
N0_ / (c.
floor * referenceCP));
331 adjustedConversionRatio = std::min(adjustedConversionRatio,
N0_ / (c.
globalFloor * fx));
333 adjustedConversionRatio = std::max(
334 currentCr, adjustedConversionRatio != QL_MAX_REAL ? adjustedConversionRatio : -QL_MAX_REAL);
335 currentCr = std::max(currentCr, adjustedConversionRatio);
337 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_adjustedCr"] = currentCr;
342 Size index =
grid_.index(
time(event.first));
352 for (Size i = index + 1; i <
grid_.size(); ++i) {
355 haveStochasticCr =
true;
359 if (event.second.dd !=
nullptr) {
381 additionalResults_[
"historicEvents.crReset_DP_" + getDateStr(event.first) +
"_S"] = S;
382 additionalResults_[
"historicEvents.crReset_DP_" + getDateStr(event.first) +
"_threshold"] = H;
383 additionalResults_[
"historicEvents.crReset_DP_" + getDateStr(event.first) +
"_fxConversion"] = fx;
384 additionalResults_[
"historicEvents.crReset_DP_" + getDateStr(event.first) +
"_currentCr"] =
391 Real d = absolute ? D : D / S;
394 ? std::max(d - H, 0.0)
396 currentCr *= absolute ? S / std::max(S - C, 1E-4) : (1.0 + C);
398 Real f = std::max(S - H, 0.0) / std::max(S - D, 1E-4);
400 f = std::max(f, 1.0);
404 additionalResults_[
"historicEvents.crReset_DP_" + getDateStr(event.first) +
"_adjustedCr"] =
416 Size index =
grid_.index(
time(event.first));
424 if (lastDividendProtectionTimeIndex == Null<Size>()) {
434 lastDividendProtectionTimeIndex = index;
435 for (Size i = index + 1; i <
grid_.size(); ++i) {
438 haveStochasticCr =
true;
449 if (lastDividendProtectionTimeIndex == Null<Size>()) {
458 lastDividendProtectionTimeIndex;
461 lastDividendProtectionTimeIndex = index;
466 if (event.second.vd !=
nullptr) {
473 if (nextConvDate >
today_) {
474 bool conversionIsProhibited =
false;
479 indexEnd = indexStart;
481 QL_REQUIRE(nextConvDate != Null<Date>(),
482 "FdConvertibleBondEvents::processConversionData(): internal error: did not find a next "
486 <<
", the last conversione date should not have exercise type FromThisDateOn");
487 indexEnd =
grid_.index(
time(nextConvDate));
492 conversionIsProhibited = S * currentCr <= vd.
cocoBarrier;
499 !conversionIsProhibited;
502 QL_FAIL(
"FdConvertibleBondEvents: internal error, exercise type not recognized");
505 for (Size i = indexStart; i <= indexEnd; ++i) {
514 if (conversionIsProhibited)
533 if (event.second.newCr != Null<Real>()) {
535 currentCr =
event.second.newCr;
536 if (event.first >=
today_) {
538 if (haveStochasticCr) {
540 Size index =
grid_.index(
time(event.first));
560 if (d.exerciseDate <=
today_)
562 Size index =
grid_.index(
time(d.exerciseDate));
567 d.pepsLowerConversionRatio};
572 QL_REQUIRE(!
finalised_,
"FdConvertibleBondEvents: internal error, events already finalised");
577 hasCall_.resize(grid.size(),
false);
578 hasPut_.resize(grid.size(),
false);
609 for (Size i = 0; i <
grid_.size(); ++i) {
610 Real fx = spot * source->discount(
grid_[i]) / target->discount(
grid_[i]);
627 for (Size k = lastRedemptionIndex + 1; k <
grid_.size(); ++k) {
629 "FdConvertibleBondEvents: conversion right after last bond redemption flow not allowed");
bool hasDividendPassThrough(const Size i) const
std::vector< ConvertibleBond2::CallabilityData > registeredCallData_
std::vector< bool > hasBondCashflow_
std::vector< CallData > putData_
bool hasCall(const Size i) const
std::vector< bool > hasContingentConversion_
bool hasMandatoryConversion(const Size i) const
std::vector< bool > hasConversionReset_
bool hasContingentConversion(const Size i) const
bool hasConversion(const Size i) const
const std::set< Real > & times() const
std::vector< Real > bondFinalRedemption_
Date nextConversionDate(const Date &d) const
std::vector< Real > currentConversionRatio_
void registerMandatoryConversion(const ConvertibleBond2::MandatoryConversionData &c)
void registerBondCashflow(const QuantLib::ext::shared_ptr< CashFlow > &c)
Date nextExerciseDate(const Date &d, const std::vector< ConvertibleBond2::CallabilityData > &data) const
std::vector< Date > associatedDate_
std::vector< bool > hasConversionInfoSet_
QuantLib::ext::shared_ptr< FxIndex > fxConversion_
void registerConversionReset(const ConvertibleBond2::ConversionResetData &c)
std::vector< QuantLib::ext::shared_ptr< CashFlow > > registeredBondCashflows_
std::vector< ConvertibleBond2::MandatoryConversionData > registeredMandatoryConversionData_
void processMandatoryConversionData()
std::map< std::string, boost::any > additionalResults_
void processMakeWholeData()
std::vector< ConvertibleBond2::ConversionRatioData > registeredConversionRatioData_
std::vector< DividendPassThroughData > dividendPassThroughData_
const ConversionResetData & getConversionResetData(const Size i) const
std::vector< ConversionData > conversionData_
void registerPut(const ConvertibleBond2::CallabilityData &c)
std::vector< bool > hasCall_
std::vector< Real > bondCashflow_
std::vector< bool > hasConversion_
Real getCurrentFxConversion(const Size i) const
std::vector< bool > stochasticConversionRatio_
Real initialConversionRatio_
std::vector< bool > hasNoConversionPlane_
std::vector< CallData > callData_
void registerConversion(const ConvertibleBond2::ConversionData &c)
const MandatoryConversionData & getMandatoryConversionData(const Size i) const
void registerConversionRatio(const ConvertibleBond2::ConversionRatioData &c)
FdConvertibleBondEvents(const Date &today, const DayCounter &dc, const Real N0, const QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > &equity, const QuantLib::ext::shared_ptr< FxIndex > &fxConversion)
void processBondCashflows()
std::vector< ConvertibleBond2::ConversionData > registeredConversionData_
std::vector< ConvertibleBond2::CallabilityData > registeredPutData_
bool hasPut(const Size i) const
QuantLib::Array mw_cr_inc_x_
bool hasNoConversionPlane(const Size i) const
void finalise(const TimeGrid &grid)
bool hasConversionReset(const Size i) const
std::vector< bool > hasDividendPassThrough_
Real getInitialConversionRatio() const
std::vector< MandatoryConversionData > mandatoryConversionData_
Real time(const Date &d) const
void registerCall(const ConvertibleBond2::CallabilityData &c)
std::vector< ConversionResetData > conversionResetData_
std::vector< Real > currentFxConversion_
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equity_
Date getAssociatedDate(const Size i) const
QuantLib::Array mw_cr_inc_y_
void processExerciseData(const std::vector< ConvertibleBond2::CallabilityData > &sourceData, std::vector< bool > &targetFlags, std::vector< CallData > &targetData)
Real getCurrentConversionRatio(const Size i) const
std::vector< ConvertibleBond2::ConversionResetData > registeredConversionResetData_
const ConversionData & getConversionData(const Size i) const
std::vector< bool > hasPut_
std::vector< bool > hasMandatoryConversion_
std::vector< ConvertibleBond2::DividendProtectionData > registeredDividendProtectionData_
const DividendPassThroughData & getDividendPassThroughData(const Size i) const
bool hasBondCashflow(const Size i) const
const CallData & getCallData(const Size i) const
ConvertibleBond2::MakeWholeData registeredMakeWholeData_
Real getBondCashflow(const Size i) const
Real getBondFinalRedemption(const Size i) const
const CallData & getPutData(const Size i) const
void processConversionAndDivProtData()
QuantLib::Matrix mw_cr_inc_z_
bool hasStochasticConversionRatio(const Size i) const
void registerDividendProtection(const ConvertibleBond2::DividendProtectionData &c)
void registerMakeWhole(const ConvertibleBond2::MakeWholeData &c)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
ExerciseType exerciseType
ReferenceType referenceType
DividendType dividendType
AdjustmentStyle adjustmentStyle
boost::optional< CrIncreaseData > crIncreaseData