30#include <ql/cashflows/cashflows.hpp>
31#include <ql/time/calendars/jointcalendar.hpp>
33#include <boost/lexical_cast.hpp>
43std::map<AssetClass, std::set<std::string>>
45 data_ = originalData_;
46 data_.populateFromBondReferenceData(referenceDataManager);
47 std::map<AssetClass, std::set<std::string>> result;
48 result[AssetClass::BOND] = {data_.bondData().securityId()};
49 if (!data_.conversionData().equityUnderlying().name().empty())
50 result[AssetClass::EQ] = {data_.conversionData().equityUnderlying().name()};
65std::vector<ConvertibleBond2::CallabilityData>
66buildCallabilityData(
const ConvertibleBondData::CallabilityData& callData,
const Date& openEndDateReplacement) {
67 std::vector<ConvertibleBond2::CallabilityData> result;
68 if (callData.initialised()) {
69 QuantLib::Schedule schedule =
makeSchedule(callData.dates(), openEndDateReplacement);
70 std::vector<Date> callDatesPlusInf = schedule.dates();
71 callDatesPlusInf.push_back(Date::maxDate());
72 auto styles = buildScheduledVectorNormalised<std::string>(callData.styles(), callData.styleDates(),
73 callDatesPlusInf,
"Bermudan",
true);
74 auto prices = buildScheduledVectorNormalised<double>(callData.prices(), callData.priceDates(), callDatesPlusInf,
76 auto priceTypes = buildScheduledVectorNormalised<std::string>(callData.priceTypes(), callData.priceTypeDates(),
77 callDatesPlusInf,
"Clean",
true);
78 auto includeAccrual = buildScheduledVectorNormalised<bool>(
79 callData.includeAccrual(), callData.includeAccrualDates(), callDatesPlusInf,
true,
true);
80 auto isSoft = buildScheduledVectorNormalised<bool>(callData.isSoft(), callData.isSoftDates(), callDatesPlusInf,
82 auto triggerRatios = buildScheduledVectorNormalised<double>(
83 callData.triggerRatios(), callData.triggerRatioDates(), callDatesPlusInf, 0.0,
true);
84 auto nOfMTriggers = buildScheduledVectorNormalised<std::string>(
85 callData.nOfMTriggers(), callData.nOfMTriggerDates(), callDatesPlusInf,
"0-of-0",
true);
87 for (Size i = 0; i < callDatesPlusInf.size() - 1; ++i) {
90 if (styles[i] ==
"Bermudan") {
91 exerciseType = ConvertibleBond2::CallabilityData::ExerciseType::OnThisDate;
92 }
else if (styles[i] ==
"American") {
94 callDatesPlusInf.size() > 2,
95 "for exercise style 'American' at least two dates (start, end) are required (call/put data)");
96 if (i == callDatesPlusInf.size() - 2)
97 exerciseType = ConvertibleBond2::CallabilityData::ExerciseType::OnThisDate;
99 exerciseType = ConvertibleBond2::CallabilityData::ExerciseType::FromThisDateOn;
101 QL_FAIL(
"invalid exercise style '" << styles[i] <<
"', expected Bermudan, American (call/put data)");
103 if (priceTypes[i] ==
"Clean") {
104 priceType = ConvertibleBond2::CallabilityData::PriceType::Clean;
105 }
else if (priceTypes[i] ==
"Dirty") {
106 priceType = ConvertibleBond2::CallabilityData::PriceType::Dirty;
108 QL_FAIL(
"invalid price type '" << priceTypes[i] <<
"', expected Clean, Dirty");
111 includeAccrual[i], isSoft[i], triggerRatios[i]});
119 if (callData.initialised() && callData.makeWholeData().initialised()) {
120 auto const& crData = callData.makeWholeData().conversionRatioIncreaseData();
121 if (crData.initialised()) {
125 std::vector<Date> dates;
126 std::transform(crData.crIncreaseDates().begin(), crData.crIncreaseDates().end(), std::back_inserter(dates),
135std::vector<ConvertibleBond2::ConversionRatioData>
136buildConversionRatioData(
const ConvertibleBondData::ConversionData& conversionData) {
137 std::vector<ConvertibleBond2::ConversionRatioData> result;
139 if (conversionData.initialised()) {
140 for (Size i = 0; i < conversionData.conversionRatios().
size(); ++i) {
141 Date d = conversionData.conversionRatioDates()[i].empty()
143 :
parseDate(conversionData.conversionRatioDates()[i]);
144 result.push_back({d, conversionData.conversionRatios()[i]});
149 QL_REQUIRE(tmp.size() == result.size(),
"Found " << result.size() <<
" conversion ratio definitions, but only "
151 <<
" unique start dates, please check for duplicates");
155std::vector<ConvertibleBond2::ConversionRatioData>
156buildConversionFixedAmountData(
const ConvertibleBondData::ConversionData& conversionData) {
157 std::vector<ConvertibleBond2::ConversionRatioData> result;
159 if (conversionData.initialised()) {
160 for (Size i = 0; i < conversionData.fixedAmountConversionData().amounts().
size(); ++i) {
161 Date d = conversionData.fixedAmountConversionData().amountDates()[i].empty()
163 :
parseDate(conversionData.fixedAmountConversionData().amountDates()[i]);
164 result.push_back({d, conversionData.fixedAmountConversionData().amounts()[i]});
169 QL_REQUIRE(tmp.size() == result.size(),
"Found " << result.size()
170 <<
" conversion fixed amount definitions, but only " << tmp.size()
171 <<
" unique start dates, please check for duplicates");
176Calendar getEqFxFixingCalendar(
const QuantLib::ext::shared_ptr<EquityIndex2>& equity,
const QuantLib::ext::shared_ptr<FxIndex>& fx) {
177 if (fx ==
nullptr && equity ==
nullptr) {
178 return NullCalendar();
179 }
else if (fx ==
nullptr && equity !=
nullptr) {
180 return equity->fixingCalendar();
181 }
else if (fx !=
nullptr && equity ==
nullptr) {
182 return fx->fixingCalendar();
184 return JointCalendar(equity->fixingCalendar(), fx->fixingCalendar());
189std::vector<ConvertibleBond2::ConversionData>
190buildConversionData(
const ConvertibleBondData::ConversionData& conversionData, RequiredFixings& requiredFixings,
191 const QuantLib::ext::shared_ptr<EquityIndex2>& equity,
const QuantLib::ext::shared_ptr<FxIndex>& fx,
192 const std::string& fxIndexName,
const Date& openEndDateReplacement) {
193 std::vector<ConvertibleBond2::ConversionData> result;
194 auto fixingCalendar = getEqFxFixingCalendar(equity, fx);
195 if (conversionData.initialised() && conversionData.dates().hasData()) {
196 QuantLib::Schedule schedule =
makeSchedule(conversionData.dates(), openEndDateReplacement);
197 std::vector<Date> conversionDatesPlusInf = schedule.dates();
198 conversionDatesPlusInf.push_back(Date::maxDate());
199 auto styles = buildScheduledVectorNormalised<std::string>(conversionData.styles(), conversionData.styleDates(),
200 conversionDatesPlusInf,
"Bermudan",
true);
203 auto cocoObservations = buildScheduledVectorNormalised<std::string>(
204 conversionData.contingentConversionData().observations(),
205 conversionData.contingentConversionData().observationDates(), conversionDatesPlusInf,
"None",
true);
206 auto cocoBarriers = buildScheduledVectorNormalised<double>(
207 conversionData.contingentConversionData().barriers(),
208 conversionData.contingentConversionData().barrierDates(), conversionDatesPlusInf, 0.0,
true);
210 for (Size i = 0; i < conversionDatesPlusInf.size() - 1; ++i) {
214 if (styles[i] ==
"Bermudan") {
215 exerciseType = ConvertibleBond2::ConversionData::ExerciseType::OnThisDate;
216 }
else if (styles[i] ==
"American") {
218 conversionDatesPlusInf.size() > 2,
219 "for exercise style 'American' at least two dates (start, end) are required (conversion data)");
220 if (i == conversionDatesPlusInf.size() - 2)
221 exerciseType = ConvertibleBond2::ConversionData::ExerciseType::OnThisDate;
223 exerciseType = ConvertibleBond2::ConversionData::ExerciseType::FromThisDateOn;
225 QL_FAIL(
"invalid exercise style '" << styles[i] <<
"', expected Bermudan, American (conversion data)");
228 QL_REQUIRE(equity !=
nullptr || cocoObservations[i] ==
"None",
229 "coco observations must be none if no equity underlying is given.");
230 if (cocoObservations[i] ==
"Spot" ||
231 exerciseType == ConvertibleBond2::ConversionData::ExerciseType::OnThisDate) {
232 cocoType = ConvertibleBond2::ConversionData::CocoType::Spot;
233 }
else if (cocoObservations[i] ==
"StartOfPeriod") {
234 cocoType = ConvertibleBond2::ConversionData::CocoType::StartOfPeriod;
236 requiredFixings.addFixingDate(fixingCalendar.adjust(conversionDatesPlusInf[i], Preceding),
237 "EQ-" + equity->name(), conversionDatesPlusInf[i + 1] + 1);
239 requiredFixings.addFixingDate(fixingCalendar.adjust(conversionDatesPlusInf[i], Preceding),
240 fxIndexName, conversionDatesPlusInf[i + 1] + 1);
242 }
else if (cocoObservations[i] ==
"None") {
243 cocoType = ConvertibleBond2::ConversionData::CocoType::None;
245 QL_FAIL(
"invalid coco observation style '" << cocoObservations[i]
246 <<
"', expected Spot, StartOfPeriod, None");
256std::vector<ConvertibleBond2::MandatoryConversionData>
257buildMandatoryConversionData(
const ConvertibleBondData::ConversionData& conversionData) {
258 std::vector<ConvertibleBond2::MandatoryConversionData> result;
259 if (conversionData.initialised() && conversionData.mandatoryConversionData().initialised()) {
260 if (conversionData.mandatoryConversionData().type() ==
"PEPS") {
261 QL_REQUIRE(conversionData.mandatoryConversionData().pepsData().initialised(),
262 "expected peps detail data for mandatory conversion");
264 parseDate(conversionData.mandatoryConversionData().date()),
265 conversionData.mandatoryConversionData().pepsData().upperBarrier(),
266 conversionData.mandatoryConversionData().pepsData().lowerBarrier(),
267 conversionData.mandatoryConversionData().pepsData().upperConversionRatio(),
268 conversionData.mandatoryConversionData().pepsData().lowerConversionRatio()});
270 QL_FAIL(
"invalid mandatory conversion type '" << conversionData.mandatoryConversionData().type()
271 <<
"', expected PEPS");
277std::vector<ConvertibleBond2::ConversionResetData>
278buildConversionResetData(
const ConvertibleBondData::ConversionData& conversionData, RequiredFixings& requiredFixings,
279 const QuantLib::ext::shared_ptr<EquityIndex2>& equity,
const QuantLib::ext::shared_ptr<FxIndex>& fx,
280 const std::string& fxIndexName,
const Date& openEndDateReplacement) {
281 std::vector<ConvertibleBond2::ConversionResetData> result;
282 auto fixingCalendar = getEqFxFixingCalendar(equity, fx);
283 if (conversionData.initialised() && conversionData.conversionResetData().initialised()) {
284 QL_REQUIRE(equity !=
nullptr || !conversionData.conversionResetData().initialised(),
285 "no conversion reset data must be specified if no equity underlying is given.");
286 QuantLib::Schedule schedule =
287 makeSchedule(conversionData.conversionResetData().dates(), openEndDateReplacement);
288 std::vector<Date> resetDatesPlusInf = schedule.dates();
289 resetDatesPlusInf.push_back(Date::maxDate());
290 auto references = buildScheduledVectorNormalised<std::string>(
291 conversionData.conversionResetData().references(), conversionData.conversionResetData().referenceDates(),
292 resetDatesPlusInf,
"InitialConversionPrice",
true);
293 auto thresholds = buildScheduledVectorNormalised<double>(conversionData.conversionResetData().thresholds(),
294 conversionData.conversionResetData().thresholdDates(),
295 resetDatesPlusInf, 0.0,
true);
296 auto gearings = buildScheduledVectorNormalised<double>(conversionData.conversionResetData().gearings(),
297 conversionData.conversionResetData().gearingDates(),
298 resetDatesPlusInf, 0.0,
true);
299 auto floors = buildScheduledVectorNormalised<double>(conversionData.conversionResetData().floors(),
300 conversionData.conversionResetData().floorDates(),
301 resetDatesPlusInf, 0.0,
true);
302 auto globalFloors = buildScheduledVectorNormalised<double>(
303 conversionData.conversionResetData().globalFloors(),
304 conversionData.conversionResetData().globalFloorDates(), resetDatesPlusInf, 0.0,
true);
305 for (Size i = 0; i < resetDatesPlusInf.size() - 1; ++i) {
306 QL_REQUIRE(gearings[i] > 0.0 ||
close_enough(gearings[i], 0.0),
307 "conversion reset gearing at " << QuantLib::io::iso_date(resetDatesPlusInf[i])
308 <<
" must be non-negative (got " << gearings[i] <<
")");
309 QL_REQUIRE(floors[i] > 0.0 ||
close_enough(floors[i], 0.0),
310 "conversion reset floor at " << QuantLib::io::iso_date(resetDatesPlusInf[i])
311 <<
" must be non-negative (got " << floors[i]);
312 QL_REQUIRE(globalFloors[i] > 0.0 ||
close_enough(globalFloors[i], 0.0),
313 "conversion reset global floor at " << QuantLib::io::iso_date(resetDatesPlusInf[i])
314 <<
" must be non-negative (got " << globalFloors[i]);
316 if (references[i] ==
"InitialConversionPrice") {
317 referenceType = ConvertibleBond2::ConversionResetData::ReferenceType::InitialCP;
318 }
else if (references[i] ==
"CurrentConversionPrice") {
319 referenceType = ConvertibleBond2::ConversionResetData::ReferenceType::CurrentCP;
321 QL_FAIL(
"invalid conversion reset reference type '"
322 << references[i] <<
"', expected InitialConversionPrice, CurrentConversionPrice");
325 gearings[i], floors[i], globalFloors[i]});
327 requiredFixings.addFixingDate(fixingCalendar.adjust(resetDatesPlusInf[i], Preceding),
328 "EQ-" + equity->name());
330 requiredFixings.addFixingDate(fixingCalendar.adjust(resetDatesPlusInf[i], Preceding), fxIndexName);
337std::vector<ConvertibleBond2::DividendProtectionData>
338buildDividendProtectionData(
const ConvertibleBondData::DividendProtectionData& dividendProtectionData,
339 RequiredFixings& requiredFixings,
const QuantLib::ext::shared_ptr<EquityIndex2>& equity,
340 const QuantLib::ext::shared_ptr<FxIndex>& fx,
const std::string& fxIndexName,
341 const Date& openEndDateReplacement) {
342 std::vector<ConvertibleBond2::DividendProtectionData> result;
343 auto fixingCalendar = getEqFxFixingCalendar(equity, fx);
344 QL_REQUIRE(equity !=
nullptr || !dividendProtectionData.initialised(),
345 "no dividend protection data must be given if no equity underlying is given.");
346 if (dividendProtectionData.initialised()) {
347 Schedule schedule =
makeSchedule(dividendProtectionData.dates(), openEndDateReplacement);
349 schedule.dates().size() >= 2,
350 "dividend protection schedule must have at least two dates (effective dp start and first protection date)");
351 std::vector<Date> divDates(std::next(schedule.dates().begin(), 1), schedule.dates().end());
352 divDates.push_back(Date::maxDate());
353 auto styles = buildScheduledVectorNormalised<std::string>(dividendProtectionData.adjustmentStyles(),
354 dividendProtectionData.adjustmentStyleDates(),
355 divDates,
"CrUpOnly",
true);
356 auto types = buildScheduledVectorNormalised<std::string>(dividendProtectionData.dividendTypes(),
357 dividendProtectionData.dividendTypeDates(), divDates,
359 auto thresholds = buildScheduledVectorNormalised<double>(
360 dividendProtectionData.thresholds(), dividendProtectionData.thresholdDates(), divDates, 0.0,
true);
361 for (Size i = 0; i < divDates.size() - 1; ++i) {
364 if (styles[i] ==
"CrUpOnly") {
365 adjustmentStyle = ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpOnly;
366 }
else if (styles[i] ==
"CrUpDown") {
367 adjustmentStyle = ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpDown;
368 }
else if (styles[i] ==
"CrUpOnly2") {
369 adjustmentStyle = ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpOnly2;
371 types[i] ==
"Absolute",
372 "divident prototection adjustment style 'CrUpOnly2' is only allowed with dividend type 'Absolute'");
373 }
else if (styles[i] ==
"CrUpDown2") {
374 adjustmentStyle = ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpDown2;
376 types[i] ==
"Absolute",
377 "dividend prototection adjustment style 'CrUpDown2' is only allowed with dividend type 'Absolute'");
378 }
else if (styles[i] ==
"PassThroughUpOnly") {
379 adjustmentStyle = ConvertibleBond2::DividendProtectionData::AdjustmentStyle::PassThroughUpOnly;
380 }
else if (styles[i] ==
"PassThroughUpDown") {
381 adjustmentStyle = ConvertibleBond2::DividendProtectionData::AdjustmentStyle::PassThroughUpDown;
384 "invalid dividend protection adjustment style '"
386 <<
"', expected CrUpOnly, CrUpDown, CrUpOnly2, CrUpDown2, PassThroughUpOnly, PassThroughUpDown");
388 if (types[i] ==
"Absolute") {
389 dividendType = ConvertibleBond2::DividendProtectionData::DividendType::Absolute;
390 }
else if (types[i] ==
"Relative") {
391 dividendType = ConvertibleBond2::DividendProtectionData::DividendType::Relative;
393 QL_FAIL(
"invalid dividend protection dividend type '" << types[i] <<
"', expected Absolute, Relative");
397 divDates[i], adjustmentStyle, dividendType, thresholds[i]});
399 if (adjustmentStyle == ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpOnly ||
400 adjustmentStyle == ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpDown ||
401 adjustmentStyle == ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpOnly2 ||
402 adjustmentStyle == ConvertibleBond2::DividendProtectionData::AdjustmentStyle::CrUpDown2) {
403 requiredFixings.addFixingDate(fixingCalendar.adjust(schedule.dates()[i + 1], Preceding),
404 "EQ-" + equity->name());
406 requiredFixings.addFixingDate(fixingCalendar.adjust(schedule.dates()[i + 1], Preceding),
417void ConvertibleBond::build(
const QuantLib::ext::shared_ptr<ore::data::EngineFactory>& engineFactory) {
418 DLOG(
"ConvertibleBond::build() called for trade " <<
id());
422 additionalData_[
"isdaAssetClass"] = string(
"Credit");
423 additionalData_[
"isdaBaseProduct"] = string(
"");
424 additionalData_[
"isdaSubProduct"] = string(
"");
425 additionalData_[
"isdaTransaction"] = string(
"");
427 auto builder = QuantLib::ext::dynamic_pointer_cast<ConvertibleBondEngineBuilder>(engineFactory->builder(
"ConvertibleBond"));
428 QL_REQUIRE(builder,
"ConvertibleBond::build(): could not cast to ConvertibleBondBuilder, this is unexpected");
430 data_ = originalData_;
431 data_.populateFromBondReferenceData(engineFactory->referenceData());
436 underlyingBond.
build(engineFactory);
438 auto qlUnderlyingBond = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(underlyingBond.
instrument()->qlInstrument());
439 QL_REQUIRE(qlUnderlyingBond,
440 "ConvertibleBond::build(): internal error, could not cast underlying bond to QuantLib::Bond");
441 auto qlUnderlyingBondCoupons = qlUnderlyingBond->cashflows();
442 qlUnderlyingBondCoupons.erase(
443 std::remove_if(qlUnderlyingBondCoupons.begin(), qlUnderlyingBondCoupons.end(),
444 [](QuantLib::ext::shared_ptr<CashFlow> c) { return QuantLib::ext::dynamic_pointer_cast<Coupon>(c) == nullptr; }),
445 qlUnderlyingBondCoupons.end());
449 QuantLib::ext::shared_ptr<EngineBuilder> vanillaBuilder = engineFactory->builder(
"Bond");
450 QL_REQUIRE(builder,
"ConvertibleBond::build(): internal error, vanilla builder is null");
451 std::string openEndDateStr = vanillaBuilder->modelParameter(
"OpenEndDateReplacement", {},
false,
"");
456 bool isPerpetual =
false;
457 for (
auto const& d : data_.bondData().coupons()) {
458 for (
auto const& r : d.schedule().rules()) {
459 isPerpetual = isPerpetual || r.endDate().empty();
463 DLOG(
"isPerpetual=" << std::boolalpha << isPerpetual <<
", openEndDateReplacement=" << openEndDateReplacement);
467 auto config = builder->configuration(MarketContext::pricing);
469 QuantLib::ext::shared_ptr<EquityIndex2> equity;
470 if (!data_.conversionData().equityUnderlying().name().empty()) {
471 equity = *engineFactory->market()->equityCurve(data_.conversionData().equityUnderlying().name(), config);
474 QL_REQUIRE((equity ==
nullptr && data_.conversionData().fixedAmountConversionData().initialised()) ||
475 (equity !=
nullptr && !data_.conversionData().fixedAmountConversionData().initialised()),
476 "ConvertibleBondEngineBuilder::build(): exactly one of equity underlying or fixed amount conversion "
477 "must be specified");
479 QuantLib::ext::shared_ptr<FxIndex> fx;
480 if (equity !=
nullptr && !equity->currency().empty()) {
481 if (equity->currency().code() != data_.bondData().currency()) {
482 QL_REQUIRE(!data_.conversionData().fxIndex().empty(),
483 "ConvertibleBondEngineBuilder::build(): FXIndex required in conversion data, since eq ccy ("
484 << equity->currency().code() <<
") not equal bond ccy (" << data_.bondData().currency()
486 fx =
buildFxIndex(data_.conversionData().fxIndex(), data_.bondData().currency(), equity->currency().code(),
487 engineFactory->market(), config);
489 }
else if (equity ==
nullptr && data_.conversionData().fixedAmountConversionData().initialised()) {
490 if (data_.conversionData().fixedAmountConversionData().currency() != data_.bondData().currency()) {
491 QL_REQUIRE(!data_.conversionData().fxIndex().empty(),
492 "ConvertibleBondEngineBuilder::build(): FXIndex required in conversion data, since fixed amount "
494 << data_.conversionData().fixedAmountConversionData().currency() <<
") not equal bond ccy ("
495 << data_.bondData().currency() <<
")");
496 fx =
buildFxIndex(data_.conversionData().fxIndex(), data_.bondData().currency(),
497 data_.conversionData().fixedAmountConversionData().currency(), engineFactory->market(),
506 Date d0 = qlUnderlyingBond->startDate();
507 Date d1 = qlUnderlyingBond->maturityDate();
510 if (equity !=
nullptr) {
511 auto div = equity->dividendFixings();
512 for (
auto const& d : div) {
513 if (d.exDate >= d0) {
514 requiredFixings_.addFixingDate(fx->fixingCalendar().adjust(d.exDate, Preceding),
515 data_.conversionData().fxIndex());
520 Date today = Settings::instance().evaluationDate();
521 d0 = std::min(d0, today);
525 for (Date d = d0; d <= d1; ++d) {
526 requiredFixings_.addFixingDate(fx->fixingCalendar().adjust(d, Preceding), data_.conversionData().fxIndex(),
527 Date::maxDate(),
false,
false);
533 Real multiplier = data_.bondData().bondNotional() * (data_.bondData().isPayer() ? -1.0 : 1.0);
538 std::vector<ConvertibleBond2::CallabilityData> callData =
539 buildCallabilityData(data_.callData(), openEndDateReplacement);
541 std::vector<ConvertibleBond2::CallabilityData> putData =
542 buildCallabilityData(data_.putData(), openEndDateReplacement);
545 std::vector<ConvertibleBond2::ConversionRatioData> conversionRatioData =
546 equity !=
nullptr ? buildConversionRatioData(data_.conversionData())
547 : buildConversionFixedAmountData(data_.conversionData());
548 std::vector<ConvertibleBond2::ConversionData> conversionData = buildConversionData(
549 data_.conversionData(), requiredFixings_, equity, fx, data_.conversionData().fxIndex(), openEndDateReplacement);
550 std::vector<ConvertibleBond2::MandatoryConversionData> mandatoryConversionData =
551 buildMandatoryConversionData(data_.conversionData());
552 std::vector<ConvertibleBond2::ConversionResetData> conversionResetData = buildConversionResetData(
553 data_.conversionData(), requiredFixings_, equity, fx, data_.conversionData().fxIndex(), openEndDateReplacement);
554 std::vector<ConvertibleBond2::DividendProtectionData> dividendProtectionData =
555 buildDividendProtectionData(data_.dividendProtectionData(), requiredFixings_, equity, fx,
556 data_.conversionData().fxIndex(), openEndDateReplacement);
561 Date lastDate = qlUnderlyingBond->maturityDate();
562 if (!dividendProtectionData.empty()) {
563 lastDate = std::max(lastDate, dividendProtectionData.back().protectionDate);
566 QL_REQUIRE(data_.conversionData().initialised(),
"ConvertibleBond::build(): conversion data required");
567 auto qlConvertible = QuantLib::ext::make_shared<ConvertibleBond2>(
568 qlUnderlyingBond->settlementDays(), qlUnderlyingBond->calendar(), qlUnderlyingBond->issueDate(),
569 qlUnderlyingBondCoupons, exchangeableData, callData, makeWholeCrIncreaseData, putData, conversionRatioData,
570 conversionData, mandatoryConversionData, conversionResetData, dividendProtectionData,
571 data_.detachable().empty() ?
false :
parseBool(data_.detachable()), isPerpetual);
572 qlConvertible->setPricingEngine(builder->engine(
573 id(), data_.bondData().currency(), data_.bondData().creditCurveId(), data_.bondData().hasCreditRisk(),
574 data_.bondData().securityId(), data_.bondData().referenceCurveId(), exchangeableData.
isExchangeable, equity, fx,
575 data_.conversionData().exchangeableData().equityCreditCurve(), qlUnderlyingBond->startDate(), lastDate));
576 setSensitivityTemplate(*builder);
580 instrument_ = QuantLib::ext::make_shared<VanillaInstrument>(qlConvertible, multiplier);
581 npvCurrency_ = notionalCurrency_ = data_.bondData().currency();
582 maturity_ = qlUnderlyingBond->maturityDate();
583 notional_ = qlUnderlyingBond->notional();
584 legs_ = {qlUnderlyingBond->cashflows()};
585 legCurrencies_ = {npvCurrency_};
586 legPayers_ = {data_.bondData().isPayer()};
590 Trade::fromXML(node);
591 originalData_.fromXML(XMLUtils::getChildNode(node,
"ConvertibleBondData"));
592 data_ = originalData_;
596 XMLNode* node = Trade::toXML(doc);
597 XMLUtils::appendNode(node, originalData_.toXML(doc));
601void ConvertibleBondTrsUnderlyingBuilder::build(
602 const std::string& parentId,
const QuantLib::ext::shared_ptr<Trade>& underlying,
const std::vector<Date>& valuationDates,
603 const std::vector<Date>& paymentDates,
const std::string& fundingCurrency,
604 const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory, QuantLib::ext::shared_ptr<QuantLib::Index>& underlyingIndex,
605 Real& underlyingMultiplier, std::map<std::string, double>& indexQuantities,
606 std::map<std::string, QuantLib::ext::shared_ptr<QuantExt::FxIndex>>& fxIndices, Real& initialPrice,
607 std::string& assetCurrency, std::string& creditRiskCurrency,
608 std::map<std::string, SimmCreditQualifierMapping>& creditQualifierMapping, Date&
maturity,
609 const std::function<QuantLib::ext::shared_ptr<QuantExt::FxIndex>(
610 const QuantLib::ext::shared_ptr<Market> market,
const std::string& configuration,
const std::string& domestic,
611 const std::string& foreign, std::map<std::string, QuantLib::ext::shared_ptr<QuantExt::FxIndex>>& fxIndices)>&
613 const std::string& underlyingDerivativeId,
RequiredFixings& fixings, std::vector<Leg>& returnLegs)
const {
614 auto t = QuantLib::ext::dynamic_pointer_cast<ore::data::ConvertibleBond>(underlying);
615 QL_REQUIRE(t,
"could not cast to ore::data::Bond, this is unexpected");
616 auto qlBond = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(underlying->instrument()->qlInstrument());
617 QL_REQUIRE(qlBond,
"expected QuantLib::Bond, could not cast");
619 BondIndexBuilder bondIndexBuilder(t->bondData(),
true,
false, NullCalendar(),
true, engineFactory);
620 underlyingIndex = bondIndexBuilder.
bondIndex();
622 underlyingIndex = bondIndexBuilder.
bondIndex();
623 underlyingMultiplier = t->data().bondData().bondNotional();
625 indexQuantities[underlyingIndex->name()] = underlyingMultiplier;
626 if (initialPrice != Null<Real>())
627 initialPrice = qlBond->notional(valuationDates.front()) * bondIndexBuilder.
priceAdjustment(initialPrice);
629 assetCurrency = t->data().bondData().currency();
630 auto fxIndex = getFxIndex(engineFactory->market(), engineFactory->configuration(MarketContext::pricing),
631 assetCurrency, fundingCurrency, fxIndices);
633 makeBondTRSLeg(valuationDates, paymentDates, bondIndexBuilder, initialPrice, fxIndex);
636 returnLegs.push_back(returnLeg);
639 creditRiskCurrency = t->data().bondData().currency();
642 creditQualifierMapping[t->bondData().creditCurveId()] =
648void ConvertibleBondTrsUnderlyingBuilder::updateUnderlying(
const QuantLib::ext::shared_ptr<ReferenceDataManager>& refData,
649 QuantLib::ext::shared_ptr<Trade>& underlying,
650 const std::string& parentId)
const {
656 if (underlying->tradeType() ==
"Bond") {
657 auto bond = QuantLib::ext::dynamic_pointer_cast<ore::data::Bond>(underlying);
658 QL_REQUIRE(bond,
"TRS::build(): internal error, could not cast underlying trade to bond");
659 if (refData !=
nullptr &&
660 refData->hasData(ConvertibleBondReferenceDatum::TYPE, bond->bondData().securityId())) {
661 DLOG(
"Underlying trade type is bond, but security id '"
662 << bond->bondData().securityId()
663 <<
"' points to convertible bond in ref data, so we change the underlying trade type accordingly.");
665 underlying->id() = parentId +
"_underlying";
670BondBuilder::Result ConvertibleBondBuilder::build(
const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory,
671 const QuantLib::ext::shared_ptr<ReferenceDataManager>& referenceData,
672 const std::string& securityId)
const {
675 data.populateFromBondReferenceData(referenceData);
677 bond.id() =
"ConvertibleBondBuilder_" + securityId +
"_" + std::to_string(
id++);
678 bond.build(engineFactory);
680 QL_REQUIRE(bond.instrument(),
"ConvertibleBondBuilder: constructed bond is null, this is unexpected");
681 auto qlBond = QuantLib::ext::dynamic_pointer_cast<QuantLib::Bond>(bond.instrument()->qlInstrument());
684 bond.instrument() && bond.instrument()->qlInstrument(),
685 "ConvertibleBondBuilder: constructed bond trade does not provide a valid ql instrument, this is unexpected "
686 "(either the instrument wrapper or the ql instrument is null)");
698 auto builders = engineFactory->modelBuilders();
699 auto b = std::find_if(builders.begin(), builders.end(),
700 [&bond](
const std::pair<
const std::string&,
const QuantLib::ext::shared_ptr<ModelBuilder>>& p) {
701 return bond.id() == p.first;
703 QL_REQUIRE(b != builders.end(),
"ConvertibleBondBuilder: could not get model builder for bond '"
704 << bond.id() <<
"' from engine factory - this is an internal error.");
Interface for building a bond index.
virtual void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Trade interface.
QuantLib::Real priceAdjustment(QuantLib::Real price)
QuantLib::ext::shared_ptr< QuantExt::BondIndex > bondIndex() const
void addRequiredFixings(RequiredFixings &requiredFixings, Leg leg={})
bool isExchangeable() const
const ExchangeableData & exchangeableData() const
std::map< AssetClass, std::set< std::string > > underlyingIndices(const QuantLib::ext::shared_ptr< ReferenceDataManager > &referenceDataManager=nullptr) const override
Add underlying Bond names.
Serializable object holding generic trade data, reporting dimensions.
const RequiredFixings & requiredFixings() const
const QuantLib::ext::shared_ptr< InstrumentWrapper > & instrument() const
Small XML Document wrapper class.
Convertible Bond trade data model and serialization.
Calendar parseCalendar(const string &s)
Convert text to QuantLib::Calendar.
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
bool parseBool(const string &s)
Convert text to bool.
Real parseReal(const string &s)
Convert text to Real.
#define DLOG(text)
Logging Macro (Level = Debug)
market data related utilties
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Size size(const ValueType &v)
boost::bimap< std::string, TRS::FundingData::NotionalType > types
Date getOpenEndDateReplacement(const std::string &replacementPeriodStr, const Calendar &calendar)
QuantLib::ext::shared_ptr< QuantExt::FxIndex > buildFxIndex(const string &fxIndex, const string &domestic, const string &foreign, const QuantLib::ext::shared_ptr< Market > &market, const string &configuration, bool useXbsCurves)
std::string securitySpecificCreditCurveName(const std::string &securityId, const std::string &creditCurveId)
Schedule makeSchedule(const ScheduleDates &data)
Leg makeBondTRSLeg(const std::vector< Date > &valuationDates, const std::vector< Date > &paymentDates, const BondIndexBuilder &bondIndexBuilder, Real initialPrice, QuantLib::ext::shared_ptr< QuantExt::FxIndex > fxIndex)
Serializable Credit Default Swap.
Map text representations to QuantLib/QuantExt types.
Reference data model and serialization.
boost::optional< CrIncreaseData > crIncreaseData
QuantLib::ext::shared_ptr< QuantExt::ModelBuilder > modelBuilder
std::string creditCurveId
QuantLib::ext::shared_ptr< QuantLib::Bond > bond
QuantExt::BondIndex::PriceQuoteMethod priceQuoteMethod
double priceQuoteBaseValue