244 {
245
246
247
248 std::set<std::pair<Date, Real>> crSchedule;
250 crSchedule.insert(std::make_pair(d.fromDate, d.conversionRatio));
251 }
252 if (!crSchedule.empty()) {
254 }
257
258
259
260
261
262
263
264 struct AdjEvent {
265 ConvertibleBond2::ConversionResetData* cd = nullptr;
266 ConvertibleBond2::DividendProtectionData* dd = nullptr;
267 ConvertibleBond2::ConversionData* vd = nullptr;
268 Real newCr = Null<Real>();
269 };
270
271 std::map<Date, AdjEvent> adjEvents;
272
274 adjEvents[d.resetDate].cd = &d;
275 }
276
278 adjEvents[d.protectionDate].dd = &d;
279 }
280
282 adjEvents[d.fromDate].newCr = d.conversionRatio;
283 }
284
286 adjEvents[d.exerciseDate].vd = &d;
287 }
288
289
290
292 Size lastDividendProtectionTimeIndex = Null<Size>();
293 bool haveStochasticCr = false;
294
295 for (auto const& event : adjEvents) {
296
297 if (event.second.cd != nullptr) {
298 const ConvertibleBond2::ConversionResetData& c = *event.second.cd;
299 if (c.resetDate <=
today_) {
300
301
302
303 Real S =
equity_->fixing(
equity_->fixingCalendar().adjust(c.resetDate, Preceding));
304 Real fx = 1.0;
307 .adjust(c.resetDate, Preceding));
308 }
310 ? currentCr
312 Real referenceCP =
N0_ / cr;
314 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_threshold"] = c.threshold;
315 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_referenceCP"] = referenceCP;
316 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_gearing"] = c.gearing;
317 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_floor"] = c.floor;
318 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_globalFloor"] =
319 c.globalFloor * fx;
320 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_fxConversion"] = fx;
321 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_currentCr"] = currentCr;
322 if (!
close_enough(cr, 0.0) && S < c.threshold * referenceCP) {
323 Real adjustedConversionRatio = QL_MAX_REAL;
325 adjustedConversionRatio = std::min(adjustedConversionRatio,
N0_ / (c.gearing * S));
326 }
328 adjustedConversionRatio = std::min(adjustedConversionRatio,
N0_ / (c.floor * referenceCP));
329 }
331 adjustedConversionRatio = std::min(adjustedConversionRatio,
N0_ / (c.globalFloor * fx));
332 }
333 adjustedConversionRatio = std::max(
334 currentCr, adjustedConversionRatio != QL_MAX_REAL ? adjustedConversionRatio : -QL_MAX_REAL);
335 currentCr = std::max(currentCr, adjustedConversionRatio);
336 }
337 additionalResults_[
"historicEvents.crReset_" + getDateStr(event.first) +
"_adjustedCr"] = currentCr;
338 } else {
339
340
341
342 Size index =
grid_.index(
time(event.first));
345 const ConvertibleBond2::ConversionResetData& c = *event.second.cd;
352 for (Size i = index + 1; i <
grid_.size(); ++i) {
354 }
355 haveStochasticCr = true;
356 }
357 }
358
359 if (event.second.dd != nullptr) {
360 const ConvertibleBond2::DividendProtectionData& c = *event.second.dd;
361 if (c.protectionDate <=
today_) {
366
367
368
369 Real fx = 1.0;
373 .adjust(c.protectionDate, Preceding));
374 }
375 Real S =
equity_->fixing(
equity_->fixingCalendar().adjust(c.protectionDate, Preceding));
376 Real D =
equity_->dividendsBetweenDates(c.startDate, c.protectionDate);
377 Real H = c.threshold * fx;
378
380 getDateStr(c.startDate) + "_" + getDateStr(c.protectionDate)] = D;
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"] =
385 currentCr;
386
389 bool absolute =
391 Real d = absolute ? D : D / S;
392 Real C =
394 ? std::max(d - H, 0.0)
395 : d - H;
396 currentCr *= absolute ? S / std::max(S - C, 1E-4) : (1.0 + C);
397 } else {
398 Real f = std::max(S - H, 0.0) / std::max(S - D, 1E-4);
400 f = std::max(f, 1.0);
401 currentCr *= f;
402 }
403
404 additionalResults_[
"historicEvents.crReset_DP_" + getDateStr(event.first) +
"_adjustedCr"] =
405 currentCr;
406 }
407 } else {
408
413
414
415
416 Size index =
grid_.index(
time(event.first));
419 const ConvertibleBond2::DividendProtectionData& c = *event.second.dd;
424 if (lastDividendProtectionTimeIndex == Null<Size>()) {
428 additionalResults_[
"historicEvents.accruedDividends_" + getDateStr(c.startDate) +
"_" +
430 } else {
433 }
434 lastDividendProtectionTimeIndex = index;
435 for (Size i = index + 1; i <
grid_.size(); ++i) {
437 }
438 haveStochasticCr = true;
439
440 } else {
441
442
443 Size index =
grid_.index(
time(c.protectionDate));
449 if (lastDividendProtectionTimeIndex == Null<Size>()) {
453 additionalResults_[
"historicEvents.accruedDividends_" + getDateStr(c.startDate) +
"_" +
456 } else {
458 lastDividendProtectionTimeIndex;
460 }
461 lastDividendProtectionTimeIndex = index;
462 }
463 }
464 }
465
466 if (event.second.vd != nullptr) {
467
468
469
470 const ConvertibleBond2::ConversionData& vd = *event.second.vd;
471
473 if (nextConvDate >
today_) {
474 bool conversionIsProhibited = false;
475 Size indexStart =
grid_.index(
time(std::max(vd.exerciseDate,
today_)));
476 Size indexEnd;
479 indexEnd = indexStart;
481 QL_REQUIRE(nextConvDate != Null<Date>(),
482 "FdConvertibleBondEvents::processConversionData(): internal error: did not find a next "
483 "conversion "
484 "date after "
485 << vd.exerciseDate
486 << ", the last conversione date should not have exercise type FromThisDateOn");
487 indexEnd =
grid_.index(
time(nextConvDate));
488
490 vd.exerciseDate <=
today_) {
491 Real S =
equity_->fixing(
equity_->fixingCalendar().adjust(vd.exerciseDate, Preceding));
492 conversionIsProhibited = S * currentCr <= vd.cocoBarrier;
494 additionalResults_[
"historicEvents.coco_" + getDateStr(vd.exerciseDate) +
"_currentCr"] =
495 currentCr;
496 additionalResults_[
"historicEvents.coco_" + getDateStr(vd.exerciseDate) +
"_cocoBarrier"] =
497 vd.cocoBarrier;
498 additionalResults_[
"historicEvents.coco_" + getDateStr(vd.exerciseDate) +
"_triggered"] =
499 !conversionIsProhibited;
500 }
501 } else {
502 QL_FAIL("FdConvertibleBondEvents: internal error, exercise type not recognized");
503 }
504
505 for (Size i = indexStart; i <= indexEnd; ++i) {
506
507
508
510 continue;
511
513
514 if (conversionIsProhibited)
515 continue;
516
518
520
521
524 }
else if (vd.exerciseDate >
today_ &&
528 }
529 }
530 }
531 }
532
533 if (event.second.newCr != Null<Real>()) {
534
535 currentCr = event.second.newCr;
536 if (event.first >=
today_) {
538 if (haveStochasticCr) {
539
540 Size index =
grid_.index(
time(event.first));
544 }
545 }
546 }
547
548
549
552 }
553
554 }
555
556}
Date nextConversionDate(const Date &d) const