213 {
214
215 Real phirr = riskReversalInFavorOf == Option::Call ? 1.0 : -1.0;
216 QuantLib::ext::shared_ptr<SimpleDeltaInterpolatedSmile> resultSmile;
217
218 if (!butterflyIsBrokerStyle) {
219
220
221
222 std::vector<Real> vol_p, vol_c;
223
224 for (Size i = 0; i < deltas.size(); ++i) {
225 QL_REQUIRE(atmVol + bfQuotes[i] - 0.5 * std::abs(rrQuotes[i]) > 0.0,
226 "createSmile: atmVol ("
227 << atmVol << ") + bf (" << bfQuotes[i] << ") - rr (" << rrQuotes[i]
228 << ") must be positive when creating smile from smile bf quotes, tte=" << expiryTime);
229 vol_p.push_back(atmVol + bfQuotes[i] - 0.5 * phirr * rrQuotes[i]);
230 vol_c.push_back(atmVol + bfQuotes[i] + 0.5 * phirr * rrQuotes[i]);
231 }
232
233
234
235 resultSmile = QuantLib::ext::make_shared<SimpleDeltaInterpolatedSmile>(
236 spot, domDisc, forDisc, expiryTime, deltas, vol_p, vol_c, atmVol, dt, at, smileInterpolation);
237
238 } else {
239
240 Real forward = spot / domDisc * forDisc;
241
242
243
244
245 std::vector<Real> kb_c, kb_p, vb;
246
247 for (Size i = 0; i < deltas.size(); ++i) {
248 Real stddevb = (atmVol + bfQuotes[i]) * std::sqrt(expiryTime);
249 QL_REQUIRE(stddevb > 0.0,
250 "createSmile: atmVol ("
251 << atmVol << ") + bf (" << bfQuotes[i]
252 << ") must be positive when creating smile from broker bf quotes, tte=" << expiryTime);
253 BlackDeltaCalculator cp(Option::Type::Put, dt, spot, domDisc, forDisc, stddevb);
254 BlackDeltaCalculator cc(Option::Type::Call, dt, spot, domDisc, forDisc, stddevb);
255 kb_p.push_back(cp.strikeFromDelta(-deltas[i]));
256 kb_c.push_back(cc.strikeFromDelta(deltas[i]));
257 vb.push_back(blackFormula(Option::Put, kb_p.back(), forward, stddevb) +
258 blackFormula(Option::Call, kb_c.back(), forward, stddevb));
259 }
260
261
262
263
264 Array guess(deltas.size());
265 for (Size i = 0; i < deltas.size(); ++i) {
266 guess[i] = std::log(std::max(0.0001, bfQuotes[i] - 0.5 * std::abs(rrQuotes[i]) + atmVol));
267 }
268
269
270
271 struct TargetFunction : public QuantLib::CostFunction {
272 TargetFunction(Real atmVol, Real phirr, Real spot, Real domDisc, Real forDisc, Real forward,
273 Real expiryTime, DeltaVolQuote::DeltaType dt, DeltaVolQuote::AtmType at,
274 const std::vector<Real>& rrQuotes, const std::vector<Real>& deltas,
275 const std::vector<Real>& kb_p, const std::vector<Real>& kb_c, const std::vector<Real>& vb,
276 BlackVolatilitySurfaceBFRR::SmileInterpolation smileInterpolation)
277 : atmVol(atmVol), phirr(phirr), spot(spot), domDisc(domDisc), forDisc(forDisc), forward(forward),
278 expiryTime(expiryTime), dt(dt), at(at), rrQuotes(rrQuotes), deltas(deltas), kb_p(kb_p), kb_c(kb_c),
279 vb(vb), smileInterpolation(smileInterpolation) {}
280
281 Real atmVol, phirr, spot, domDisc, forDisc, forward, expiryTime;
282 DeltaVolQuote::DeltaType dt;
283 DeltaVolQuote::AtmType at;
284 const std::vector<Real>&rrQuotes, deltas, kb_p, kb_c, vb;
285 BlackVolatilitySurfaceBFRR::SmileInterpolation smileInterpolation;
286
287 mutable Real bestValue = QL_MAX_REAL;
288 mutable QuantLib::ext::shared_ptr<SimpleDeltaInterpolatedSmile> bestSmile;
289
290 Array values(const Array& x) const override {
291
292 constexpr Real large_error = 1E6;
293
294 Array smileBfVol(x.size());
295 for (Size i = 0; i < x.size(); ++i)
296 smileBfVol[i] = std::exp(x[i]) + 0.5 * std::abs(rrQuotes[i]) - atmVol;
297
298
299
300 std::vector<Real> vol_c, vol_p;
301
302 for (Size i = 0; i < deltas.size(); ++i) {
303 vol_p.push_back(atmVol + smileBfVol[i] - 0.5 * phirr * rrQuotes[i]);
304 vol_c.push_back(atmVol + smileBfVol[i] + 0.5 * phirr * rrQuotes[i]);
305 QL_REQUIRE(vol_p.back() > 0.0, " createSmile: internal error: put vol = "
306 << vol_p.back() << " during broker bf fitting");
307 QL_REQUIRE(vol_c.back() > 0.0, " createSmile: internal error: call vol = "
308 << vol_c.back() << " during broker bf fitting");
309 }
310
311
312
313 QuantLib::ext::shared_ptr<SimpleDeltaInterpolatedSmile> tmpSmile;
314 try {
315 tmpSmile = QuantLib::ext::make_shared<SimpleDeltaInterpolatedSmile>(
316 spot, domDisc, forDisc, expiryTime, deltas, vol_p, vol_c, atmVol, dt, at, smileInterpolation);
317 } catch (...) {
318
319 return Array(deltas.size(), large_error);
320 }
321
322
323
324 std::vector<Real> vs;
325 for (Size i = 0; i < deltas.size(); ++i) {
326 Real pvol, cvol;
327 try {
328 pvol = tmpSmile->volatility(kb_p[i]);
329 cvol = tmpSmile->volatility(kb_c[i]);
330 } catch (...) {
331
332 return Array(deltas.size(), large_error);
333 }
334 vs.push_back(blackFormula(Option::Put, kb_p[i], forward, pvol * std::sqrt(expiryTime)) +
335 blackFormula(Option::Call, kb_c[i], forward, cvol * std::sqrt(expiryTime)));
336 }
337
338
339
340 Array result(deltas.size());
341 for (Size i = 0; i < deltas.size(); ++i) {
342 result[i] = (vs[i] - vb[i]) / vb[i];
343 if (!std::isfinite(result[i]))
344 result[i] = large_error;
345 }
346
347 Real value = std::sqrt(std::accumulate(result.begin(), result.end(), 0.0,
348 [](Real acc, Real x) { return acc + x * x; })) /
349 result.size();
350
351 if (value < bestValue) {
352 bestValue = value;
353 bestSmile = tmpSmile;
354 }
355
356 return result;
357 }
358 };
359
360 TargetFunction targetFunction{atmVol, phirr, spot, domDisc, forDisc, forward, expiryTime, dt,
361 at, rrQuotes, deltas, kb_p, kb_c, vb, smileInterpolation};
362 NoConstraint noConstraint;
363 LevenbergMarquardt lm;
364 EndCriteria endCriteria(100, 10, 1E-8, 1E-8, 1E-8);
365 Problem problem(targetFunction, noConstraint, guess);
366 lm.minimize(problem, endCriteria);
367
368 QL_REQUIRE(targetFunction.bestValue < 0.01, "createSmile at expiry "
369 << expiryTime << " failed: target function value ("
370 << problem.functionValue() << ") not close to zero");
371
372 resultSmile = targetFunction.bestSmile;
373 }
374
375
376
377 static const std::vector<Real> samplePoints = {0.01, 0.05, 0.1, 0.2, 0.5, 0.8, 0.9, 0.95, 0.99};
378 for (auto const& simpleDelta : samplePoints) {
379 Real vol = resultSmile->volatilityAtSimpleDelta(simpleDelta);
380 QL_REQUIRE(vol > 0.0001 && vol < 5.0, "createSmile at expiry " << expiryTime << ": volatility at simple delta "
381 << simpleDelta << " (" << vol
382 << ") is not plausible.");
383 }
384
385 return resultSmile;
386}