103 {
104
105 std::vector<std::string> messages;
107 "BlackMultiLegOptionEngineBase::calculate(): instrument is not handled: "
108 << boost::algorithm::join(messages, ", "));
109
110
111
114 for (Size i = 0; i <
legs_.size(); ++i) {
115 for (Size j = 0; j <
legs_[i].size(); ++j) {
117 }
118 }
120 return;
121 }
122
123
124
125 QL_REQUIRE(
exercise_->dates().size() == 1,
126 "BlackMultiLegOptionEngineBase: expected 1 exercise dates, got " <<
exercise_->dates().size());
128 Real exerciseTime =
volatility_->timeFromReference(exerciseDate);
129
130
131
132
133 struct CfInfo {
134 Real fixedRate = Null<Real>();
135 Real floatingSpread = Null<Real>();
136 Real floatingGearing = Null<Real>();
137 Real nominal = Null<Real>();
138 Real accrual = Null<Real>();
139 Real amount = Null<Real>();
140 Real discountFactor = Null<Real>();
141 Date payDate = Null<Date>();
142 };
143
144 std::vector<CfInfo> cfInfo;
145
146 DayCounter fixedDayCount;
147 Date earliestAccrualStartDate = Date::maxDate();
148 Date latestAccrualEndDate = Date::minDate();
149
150 Size numFixed = 0, numFloat = 0;
151
152 for (Size i = 0; i <
legs_.size(); ++i) {
153 for (Size j = 0; j <
legs_[i].size(); ++j) {
154 Real payer =
payer_[i] ? -1.0 : 1.0;
155 if (
auto c = QuantLib::ext::dynamic_pointer_cast<Coupon>(
legs_[i][j])) {
156 auto fixedCoupon = QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(c);
157 if (fixedCoupon)
158 fixedDayCount = c->dayCounter();
159 if (c->accrualStartDate() >= exerciseDate) {
160 cfInfo.push_back(CfInfo());
161 earliestAccrualStartDate = std::min(earliestAccrualStartDate, c->accrualStartDate());
162 latestAccrualEndDate = std::max(latestAccrualEndDate, c->accrualEndDate());
163 if (fixedCoupon) {
164 cfInfo.back().fixedRate = fixedCoupon->rate();
165 cfInfo.back().nominal = c->nominal() * payer;
166 ++numFixed;
167 } else if (auto f = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(c)) {
168 cfInfo.back().floatingSpread = f->spread();
169 cfInfo.back().floatingGearing = f->gearing();
170 cfInfo.back().nominal = f->nominal() * payer;
171 ++numFloat;
172 }
173 cfInfo.back().accrual = c->accrualPeriod();
174 cfInfo.back().discountFactor =
discountCurve_->discount(c->date());
175 cfInfo.back().amount = c->amount() * payer;
176 cfInfo.back().payDate = c->date();
177 }
178 }
else if (
legs_[i][j]->date() > exerciseDate) {
179 cfInfo.push_back(CfInfo());
181 cfInfo.back().amount =
legs_[i][j]->amount() * payer;
182 cfInfo.back().payDate =
legs_[i][j]->date();
183 }
184 }
185 }
186
187
188
189 if (numFloat > 0 && numFixed == 0) {
190 for (auto const& c : cfInfo) {
191 if (c.floatingSpread != Null<Real>()) {
192 cfInfo.push_back(CfInfo());
193 cfInfo.back().fixedRate = 0.0;
194 cfInfo.back().nominal = c.nominal * -1;
195 cfInfo.back().accrual = c.accrual;
196 cfInfo.back().discountFactor = c.discountFactor;
197 cfInfo.back().amount = 0.0;
198 cfInfo.back().payDate = c.payDate;
199 }
200 }
201 }
202
203 QL_REQUIRE(!fixedDayCount.empty(), "BlackMultiLegOptionEngineBase::calculate(): could not determine fixed day "
204 "counter - it appears no fixed coupons are present in the underlying.");
205 QL_REQUIRE(earliestAccrualStartDate != Date::maxDate() && latestAccrualEndDate != Date::minDate(),
206 "BlackMultiLegOptionEngineBase::calculate(): could not determine earliest / latest accrual start / end "
207 "dates. No coupons seems to be present in the underlying.");
208
209
210
211 if (
auto rebatedExercise = QuantLib::ext::dynamic_pointer_cast<QuantExt::RebatedExercise>(
exercise_)) {
212 cfInfo.push_back(CfInfo());
213 cfInfo.back().amount = rebatedExercise->rebate(0);
214 cfInfo.back().discountFactor =
discountCurve_->discount(rebatedExercise->rebatePaymentDate(0));
215 }
216
217
218
219 Real weightedFixedRateNom = 0.0, weightedFixedRateDenom = 0.0;
220 Real fixedBps = 0.0, fixedNpv = 0.0;
221 for (auto const& c : cfInfo) {
222 if (c.fixedRate != Null<Real>()) {
223 Real w = c.nominal * c.accrual * c.discountFactor;
224 weightedFixedRateNom += c.fixedRate * w;
225 weightedFixedRateDenom += w;
226 fixedBps += w;
227 fixedNpv += c.amount * c.discountFactor;
228 }
229 }
230 Real weightedFixedRate = weightedFixedRateNom / weightedFixedRateDenom;
231
232
233
234 Real weightedFloatingSpreadNom = 0.0, weightedFloatingSpreadDenom = 0.0;
235 Real floatingBps = 0.0, floatingNpv = 0.0;
236 for (auto const& c : cfInfo) {
237 if (c.floatingSpread != Null<Real>()) {
238 Real w = c.nominal * c.accrual * c.discountFactor;
239 weightedFloatingSpreadNom += c.floatingSpread * w;
240 weightedFloatingSpreadDenom += w;
241 floatingBps += w;
242 floatingNpv += c.amount * c.discountFactor;
243 }
244 }
245 Real weightedFloatingSpread = weightedFloatingSpreadNom / weightedFloatingSpreadDenom;
246
247
248
249 Real simpleCfNpv = 0.0;
250 for (auto const& c : cfInfo) {
251 if (c.fixedRate == Null<Real>() && c.floatingSpread == Null<Real>()) {
252 simpleCfNpv += c.amount * c.discountFactor;
253 }
254 }
255
256
257
258 Real atmForward = -floatingNpv / fixedBps;
259
260
261
262 Real spreadCorrection = weightedFloatingSpread * std::abs(floatingBps / fixedBps);
263
264 Real effectiveAtmForward = atmForward - spreadCorrection;
265 Real effectiveFixedRate = weightedFixedRate - spreadCorrection;
266
267
268
269 Real simpleCfCorrection = 0.0;
270 for (auto const& c : cfInfo) {
271 if (c.fixedRate == Null<Real>() && c.floatingSpread == Null<Real>()) {
272 simpleCfCorrection += c.amount * c.discountFactor / fixedBps;
273 }
274 }
275
276 effectiveFixedRate += simpleCfCorrection;
277
278
279
280 Real annuity = 0.0;
283 annuity = std::abs(fixedBps);
285
286 InterestRate sr(effectiveAtmForward, fixedDayCount, Compounded, Annual);
287 for (auto const& c : cfInfo) {
288 if (c.fixedRate != Null<Real>()) {
289 annuity += std::abs(c.nominal) * c.accrual *
290 sr.discountFactor(std::min(earliestAccrualStartDate, c.payDate), c.payDate);
291 }
292 }
294 } else {
295 QL_FAIL("BlackMultiLegOptionEngine: invalid (settlementType, settlementMethod) pair");
296 }
297
298
299
300 Real swapLength =
volatility_->swapLength(earliestAccrualStartDate, latestAccrualEndDate);
301
302
303
304 swapLength = std::max(swapLength, 1.0 / 12.0);
305
307 Real displacement =
309
311 Real delta = 0.0, vega = 0.0;
312 Option::Type optionType = fixedBps > 0 ? Option::Put : Option::Call;
313
316 }
else if (
volatility_->volatilityType() == ShiftedLognormal) {
317 npv_ = blackFormula(optionType, effectiveFixedRate, effectiveAtmForward, stdDev, annuity, displacement);
318 vega = std::sqrt(exerciseTime) *
319 blackFormulaStdDevDerivative(effectiveFixedRate, effectiveAtmForward, stdDev, annuity, displacement);
320 delta = blackFormulaForwardDerivative(optionType, effectiveFixedRate, effectiveAtmForward, stdDev, annuity,
321 displacement);
322 } else {
323 npv_ = bachelierBlackFormula(optionType, effectiveFixedRate, effectiveAtmForward, stdDev, annuity);
324 vega = std::sqrt(exerciseTime) *
325 bachelierBlackFormulaStdDevDerivative(effectiveFixedRate, effectiveAtmForward, stdDev, annuity);
326 delta = bachelierBlackFormulaForwardDerivative(optionType, effectiveFixedRate, effectiveAtmForward, stdDev,
327 annuity);
328 }
329
331
351}
std::vector< Currency > currency_
QuantLib::ext::shared_ptr< Exercise > exercise_
std::map< std::string, boost::any > additionalResults_
std::vector< bool > payer_
Settlement::Method settlementMethod_
Settlement::Type settlementType_
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
RandomVariable variance(const RandomVariable &r)