50 {
51
52 QL_REQUIRE(!
discountCurve_.empty(),
"BlackBondOptionEngine::calculate(): empty discount curve");
53
54 QL_REQUIRE(
arguments_.putCallSchedule.size() == 1,
"BlackBondOptionEngine: can only handle European options");
55 Date exerciseDate =
arguments_.putCallSchedule.front()->date();
56
58
59 auto fwdBondEngine = QuantLib::ext::make_shared<DiscountingRiskyBondEngine>(
61 auto bondNpvResults = fwdBondEngine->calculateNpv(exerciseDate,
arguments_.underlying->settlementDate(exerciseDate),
63
64 for (auto& cfRes : bondNpvResults.cashflowResults) {
65 cfRes.legNumber = 0;
66 cfRes.type = "Underlying_Bond__" + cfRes.type;
67 }
68
69 Real fwdNpv = bondNpvResults.npv;
70
71
73
74
76 fwdNpv = (1.0 - knockOutProbability) * fwdNpv + knockOutProbability *
79 }
80
81
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);
87
88 QL_REQUIRE(
arguments_.putCallSchedule.size() == 1,
"BlackBondOptionEngine: only European bond options allowed");
89
90
91 Real underlyingLength =
volatility_->swapLength(exerciseDate,
arguments_.underlying->cashflows().back()->date());
92 Volatility yieldVol =
volatility_->volatility(exerciseDate, underlyingLength, fwdYtm);
93
94
95 Volatility fwdPriceVol;
96 Real shift = 0.0;
97 if (
volatility_->volatilityType() == VolatilityType::Normal)
98 fwdPriceVol = yieldVol * fwdDur;
99 else {
101 QL_REQUIRE(fwdYtm > 0, "BlackBondOptionEngine: input yield vols are lognormal, but yield is not positive ("
102 << fwdYtm << ")");
103 fwdPriceVol = yieldVol * fwdDur * fwdYtm;
104 } else {
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 ("
108 << -shift);
109 fwdPriceVol = yieldVol * fwdDur * (fwdYtm + shift);
110 }
111 }
112
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
117 ? 0.0
118 :
volatility_->shift(exerciseDate, underlyingLength)));
119
120
121 Real cashStrike;
122 if (
arguments_.putCallSchedule.front()->isBondPrice()) {
123
124
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;
128 } else {
129
130
131 InterestRate yield =
arguments_.putCallSchedule.front()->yield();
132 cashStrike = CashFlows::npv(
arguments_.underlying->cashflows(), yield,
false, exerciseDate, exerciseDate);
133 }
134
135 Real optionValue = blackFormula(
136 arguments_.putCallSchedule[0]->type() == Callability::Call ? Option::Call : Option::Put, cashStrike, fwdNpv,
138
139
141 optionValue *= 1.0 - knockOutProbability;
142
143 CashFlowResults optionFlow;
144 optionFlow.payDate = exerciseDate;
145 optionFlow.legNumber = 1;
146 optionFlow.type = "ExpectedOptionPayoff";
147 optionFlow.amount = optionValue /
discountCurve_->discount(exerciseDate);
148 optionFlow.discountFactor =
discountCurve_->discount(exerciseDate);
149 optionFlow.presentValue = optionValue;
150
151 bondNpvResults.cashflowResults.push_back(optionFlow);
152
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;
164
165 results_.additionalResults[
"AccruedAtExercise"] =
arguments_.underlying->accruedAmount(exerciseDate)/100;
166
167
169 results_.additionalResults[
"ExpectedBondRecovery"] = knockOutProbability *
171 arguments_.underlying->notional(exerciseDate);
172 }
174}
const Instrument::results * results_
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Swap::arguments * arguments_