23#include <ql/cashflows/cashflows.hpp>
24#include <ql/exercise.hpp>
25#include <ql/pricingengines/blackformula.hpp>
26#include <ql/quote.hpp>
27#include <ql/termstructures/yield/impliedtermstructure.hpp>
28#include <ql/termstructures/yieldtermstructure.hpp>
29#include <ql/time/calendars/nullcalendar.hpp>
34 const Handle<SwaptionVolatilityStructure>& volatility,
35 const Handle<YieldTermStructure>& underlyingReferenceCurve,
36 const Handle<DefaultProbabilityTermStructure>& defaultCurve,
37 const Handle<Quote>& recoveryRate,
const Handle<Quote>& securitySpread,
38 Period timestepPeriod)
39 : discountCurve_(discountCurve), volatility_(volatility), underlyingReferenceCurve_(underlyingReferenceCurve),
40 defaultCurve_(defaultCurve), recoveryRate_(recoveryRate), securitySpread_(securitySpread),
41 timestepPeriod_(timestepPeriod) {
52 QL_REQUIRE(!
discountCurve_.empty(),
"BlackBondOptionEngine::calculate(): empty discount curve");
54 QL_REQUIRE(
arguments_.putCallSchedule.size() == 1,
"BlackBondOptionEngine: can only handle European options");
55 Date exerciseDate =
arguments_.putCallSchedule.front()->date();
59 auto fwdBondEngine = QuantLib::ext::make_shared<DiscountingRiskyBondEngine>(
61 auto bondNpvResults = fwdBondEngine->calculateNpv(exerciseDate,
arguments_.underlying->settlementDate(exerciseDate),
64 for (
auto& cfRes : bondNpvResults.cashflowResults) {
66 cfRes.type =
"Underlying_Bond__" + cfRes.type;
69 Real fwdNpv = bondNpvResults.npv;
76 fwdNpv = (1.0 - knockOutProbability) * fwdNpv + knockOutProbability *
82 Rate fwdYtm = CashFlows::yield(
arguments_.underlying->cashflows(), fwdNpv,
volatility_->dayCounter(), Compounded,
83 Annual,
false, exerciseDate, exerciseDate);
84 InterestRate fwdRate(fwdYtm,
volatility_->dayCounter(), Compounded, Annual);
85 Time fwdDur = CashFlows::duration(
arguments_.underlying->cashflows(), fwdRate, Duration::Modified,
false,
86 exerciseDate, exerciseDate);
88 QL_REQUIRE(
arguments_.putCallSchedule.size() == 1,
"BlackBondOptionEngine: only European bond options allowed");
91 Real underlyingLength =
volatility_->swapLength(exerciseDate,
arguments_.underlying->cashflows().back()->date());
92 Volatility yieldVol =
volatility_->volatility(exerciseDate, underlyingLength, fwdYtm);
95 Volatility fwdPriceVol;
97 if (
volatility_->volatilityType() == VolatilityType::Normal)
98 fwdPriceVol = yieldVol * fwdDur;
101 QL_REQUIRE(fwdYtm > 0,
"BlackBondOptionEngine: input yield vols are lognormal, but yield is not positive ("
103 fwdPriceVol = yieldVol * fwdDur * fwdYtm;
105 shift =
volatility_->shift(exerciseDate, underlyingLength);
106 QL_REQUIRE(fwdYtm > -shift,
"BlackBondOptionEngine: input yield vols are shifted lognormal "
107 << shift <<
", but yield (" << fwdYtm <<
") is not greater than -shift ("
109 fwdPriceVol = yieldVol * fwdDur * (fwdYtm + shift);
113 QL_REQUIRE(fwdPriceVol >= 0.0,
"BlackBondOptionEngine: negative forward price vol ("
114 << fwdPriceVol <<
"), yieldVol=" << yieldVol <<
", fwdDur=" << fwdDur
115 <<
", fwdYtm=" << fwdYtm <<
", shift="
116 << (
volatility_->volatilityType() == VolatilityType::Normal
118 :
volatility_->shift(exerciseDate, underlyingLength)));
122 if (
arguments_.putCallSchedule.front()->isBondPrice()) {
125 cashStrike =
arguments_.putCallSchedule.front()->price().amount();
126 if (
arguments_.putCallSchedule.front()->price().type() == QuantLib::Bond::Price::Clean)
127 cashStrike +=
arguments_.underlying->accruedAmount(exerciseDate) / 100;
131 InterestRate yield =
arguments_.putCallSchedule.front()->yield();
132 cashStrike = CashFlows::npv(
arguments_.underlying->cashflows(), yield,
false, exerciseDate, exerciseDate);
135 Real optionValue = blackFormula(
136 arguments_.putCallSchedule[0]->type() == Callability::Call ? Option::Call : Option::Put, cashStrike, fwdNpv,
141 optionValue *= 1.0 - knockOutProbability;
144 optionFlow.
payDate = exerciseDate;
146 optionFlow.
type =
"ExpectedOptionPayoff";
151 bondNpvResults.cashflowResults.push_back(optionFlow);
153 results_.additionalResults[
"knockOutProbability"] = knockOutProbability;
154 results_.additionalResults[
"cashFlowResults"] = bondNpvResults.cashflowResults;
155 results_.additionalResults[
"CashStrike"] = cashStrike;
156 results_.additionalResults[
"FwdCashPrice"] = fwdNpv;
157 results_.additionalResults[
"PriceVol"] = fwdPriceVol;
159 results_.additionalResults[
"optionValue"] = optionValue;
160 results_.additionalResults[
"yieldVol"] = yieldVol;
161 results_.additionalResults[
"yieldVolShift"] = shift;
162 results_.additionalResults[
"fwdDuration"] = fwdDur;
163 results_.additionalResults[
"fwdYieldToMaturity"] = fwdYtm;
165 results_.additionalResults[
"AccruedAtExercise"] =
arguments_.underlying->accruedAmount(exerciseDate)/100;
169 results_.additionalResults[
"ExpectedBondRecovery"] = knockOutProbability *
171 arguments_.underlying->notional(exerciseDate);
Black bond option engine.
const Instrument::results * results_
Handle< YieldTermStructure > discountCurve_
BlackBondOptionEngine(const Handle< YieldTermStructure > &discountCurve, const Handle< SwaptionVolatilityStructure > &volatility, const Handle< YieldTermStructure > &underlyingReferenceCurve, const Handle< DefaultProbabilityTermStructure > &defaultCurve=Handle< DefaultProbabilityTermStructure >(), const Handle< Quote > &recoveryRate=Handle< Quote >(), const Handle< Quote > &securitySpread=Handle< Quote >(), Period timestepPeriod=1 *Months)
volatility is the quoted fwd yield volatility, not price vol
Handle< SwaptionVolatilityStructure > volatility_
void calculate() const override
Handle< Quote > securitySpread_
Handle< DefaultProbabilityTermStructure > defaultCurve_
Handle< Quote > recoveryRate_
Handle< YieldTermStructure > underlyingReferenceCurve_
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
QuantLib::Real presentValue
QuantLib::Real discountFactor
Swap::arguments * arguments_