| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2006 Mario Pucci |
| 5 | Copyright (C) 2013, 2015 Peter Caspers |
| 6 | |
| 7 | This file is part of QuantLib, a free-software/open-source library |
| 8 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 9 | |
| 10 | QuantLib is free software: you can redistribute it and/or modify it |
| 11 | under the terms of the QuantLib license. You should have received a |
| 12 | copy of the license along with this program; if not, please email |
| 13 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 14 | <http://quantlib.org/license.shtml>. |
| 15 | |
| 16 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 17 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 18 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 19 | */ |
| 20 | |
| 21 | #include <ql/math/comparison.hpp> |
| 22 | #include <ql/pricingengines/blackformula.hpp> |
| 23 | #include <ql/settings.hpp> |
| 24 | #include <ql/termstructures/volatility/smilesection.hpp> |
| 25 | #include <utility> |
| 26 | |
| 27 | using std::sqrt; |
| 28 | |
| 29 | namespace QuantLib { |
| 30 | |
| 31 | void SmileSection::update() { |
| 32 | if (isFloating_) { |
| 33 | referenceDate_ = Settings::instance().evaluationDate(); |
| 34 | initializeExerciseTime(); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | void SmileSection::initializeExerciseTime() const { |
| 39 | QL_REQUIRE(exerciseDate_>=referenceDate_, |
| 40 | "expiry date (" << exerciseDate_ << |
| 41 | ") must be greater than reference date (" << |
| 42 | referenceDate_ << ")" ); |
| 43 | exerciseTime_ = dc_.yearFraction(d1: referenceDate_, d2: exerciseDate_); |
| 44 | } |
| 45 | |
| 46 | SmileSection::SmileSection(const Date& d, |
| 47 | DayCounter dc, |
| 48 | const Date& referenceDate, |
| 49 | const VolatilityType type, |
| 50 | const Rate shift) |
| 51 | : exerciseDate_(d), dc_(std::move(dc)), volatilityType_(type), shift_(shift) { |
| 52 | isFloating_ = referenceDate==Date(); |
| 53 | if (isFloating_) { |
| 54 | registerWith(h: Settings::instance().evaluationDate()); |
| 55 | referenceDate_ = Settings::instance().evaluationDate(); |
| 56 | } else |
| 57 | referenceDate_ = referenceDate; |
| 58 | SmileSection::initializeExerciseTime(); |
| 59 | } |
| 60 | |
| 61 | SmileSection::SmileSection(Time exerciseTime, |
| 62 | DayCounter dc, |
| 63 | const VolatilityType type, |
| 64 | const Rate shift) |
| 65 | : isFloating_(false), dc_(std::move(dc)), exerciseTime_(exerciseTime), volatilityType_(type), |
| 66 | shift_(shift) { |
| 67 | QL_REQUIRE(exerciseTime_>=0.0, |
| 68 | "expiry time must be positive: " << |
| 69 | exerciseTime_ << " not allowed" ); |
| 70 | } |
| 71 | |
| 72 | Real SmileSection::optionPrice(Rate strike, |
| 73 | Option::Type type, |
| 74 | Real discount) const { |
| 75 | Real atm = atmLevel(); |
| 76 | QL_REQUIRE(atm != Null<Real>(), |
| 77 | "smile section must provide atm level to compute option price" ); |
| 78 | // if lognormal or shifted lognormal, |
| 79 | // for strike at -shift, return option price even if outside |
| 80 | // minstrike, maxstrike interval |
| 81 | if (volatilityType() == ShiftedLognormal) |
| 82 | return blackFormula(optionType: type,strike,forward: atm, stdDev: std::fabs(x: strike+shift()) < QL_EPSILON ? |
| 83 | 0.2 : Real(sqrt(x: variance(strike))),discount,displacement: shift()); |
| 84 | else |
| 85 | return bachelierBlackFormula(optionType: type,strike,forward: atm,stdDev: sqrt(x: variance(strike)),discount); |
| 86 | } |
| 87 | |
| 88 | Real SmileSection::digitalOptionPrice(Rate strike, |
| 89 | Option::Type type, |
| 90 | Real discount, |
| 91 | Real gap) const { |
| 92 | Real m = volatilityType() == ShiftedLognormal ? Real(-shift()) : -QL_MAX_REAL; |
| 93 | Real kl = std::max(a: strike-gap/2.0,b: m); |
| 94 | Real kr = kl+gap; |
| 95 | return (type==Option::Call ? 1.0 : -1.0) * |
| 96 | (optionPrice(strike: kl,type,discount)-optionPrice(strike: kr,type,discount)) / gap; |
| 97 | } |
| 98 | |
| 99 | Real SmileSection::density(Rate strike, Real discount, Real gap) const { |
| 100 | Real m = volatilityType() == ShiftedLognormal ? Real(-shift()) : -QL_MAX_REAL; |
| 101 | Real kl = std::max(a: strike-gap/2.0,b: m); |
| 102 | Real kr = kl+gap; |
| 103 | return (digitalOptionPrice(strike: kl,type: Option::Call,discount,gap) - |
| 104 | digitalOptionPrice(strike: kr,type: Option::Call,discount,gap)) / gap; |
| 105 | } |
| 106 | |
| 107 | Real SmileSection::vega(Rate strike, Real discount) const { |
| 108 | Real atm = atmLevel(); |
| 109 | QL_REQUIRE(atm != Null<Real>(), |
| 110 | "smile section must provide atm level to compute option vega" ); |
| 111 | if (volatilityType() == ShiftedLognormal) |
| 112 | return blackFormulaVolDerivative(strike,forward: atmLevel(), |
| 113 | stdDev: sqrt(x: variance(strike)), |
| 114 | expiry: exerciseTime(),discount,displacement: shift())*0.01; |
| 115 | else |
| 116 | QL_FAIL("vega for normal smilesection not yet implemented" ); |
| 117 | } |
| 118 | |
| 119 | Real SmileSection::volatility(Rate strike, VolatilityType volatilityType, |
| 120 | Real shift) const { |
| 121 | if(volatilityType == volatilityType_ && close(x: shift,y: this->shift())) |
| 122 | return volatility(strike); |
| 123 | Real atm = atmLevel(); |
| 124 | QL_REQUIRE(atm != Null<Real>(), |
| 125 | "smile section must provide atm level to compute converted volatilties" ); |
| 126 | Option::Type type = strike >= atm ? Option::Call : Option::Put; |
| 127 | Real premium = optionPrice(strike,type); |
| 128 | Real premiumAtm = optionPrice(strike: atm,type); |
| 129 | if (volatilityType == ShiftedLognormal) { |
| 130 | try { |
| 131 | return blackFormulaImpliedStdDev(optionType: type, strike, forward: atm, blackPrice: premium, |
| 132 | discount: 1.0, displacement: shift) / |
| 133 | std::sqrt(x: exerciseTime()); |
| 134 | } catch(...) { |
| 135 | return blackFormulaImpliedStdDevChambers( |
| 136 | optionType: type, strike, forward: atm, blackPrice: premium, blackAtmPrice: premiumAtm, discount: 1.0, displacement: shift) / |
| 137 | std::sqrt(x: exerciseTime()); |
| 138 | } |
| 139 | } else { |
| 140 | return bachelierBlackFormulaImpliedVol(optionType: type, strike, forward: atm, |
| 141 | tte: exerciseTime(), bachelierPrice: premium); |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | |