Skip to content

Commit

Permalink
Optionally allow using forward price for swaption implied-volatility …
Browse files Browse the repository at this point in the history
…calculation (#2025)
  • Loading branch information
lballabio authored Jul 17, 2024
2 parents 617a490 + c1241da commit ee934e4
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 16 deletions.
16 changes: 11 additions & 5 deletions ql/instruments/swaption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,19 +180,25 @@ namespace QuantLib {
}

Volatility Swaption::impliedVolatility(Real targetValue,
const Handle<YieldTermStructure>& d,
const Handle<YieldTermStructure>& discountCurve,
Volatility guess,
Real accuracy,
Natural maxEvaluations,
Volatility minVol,
Volatility maxVol,
VolatilityType type,
Real displacement) const {
//calculate();
Real displacement,
PriceType priceType) const {

QL_REQUIRE(!isExpired(), "instrument expired");
QL_REQUIRE(exercise_->type() == Exercise::European, "not a European option");

if (priceType == Forward) {
// convert to spot
targetValue *= discountCurve->discount(exercise_->date(0));
}

ImpliedSwaptionVolHelper f(*this, d, targetValue, displacement, type);
//Brent solver;
ImpliedSwaptionVolHelper f(*this, discountCurve, targetValue, displacement, type);
NewtonSafe solver;
solver.setMaxEvaluations(maxEvaluations);
return solver.solve(f, accuracy, guess, minVol, maxVol);
Expand Down
4 changes: 3 additions & 1 deletion ql/instruments/swaption.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ namespace QuantLib {
*/
class Swaption : public Option {
public:
enum PriceType { Spot, Forward };
class arguments;
class engine;
Swaption(ext::shared_ptr<FixedVsFloatingSwap> swap,
Expand Down Expand Up @@ -131,7 +132,8 @@ namespace QuantLib {
Volatility minVol = 1.0e-7,
Volatility maxVol = 4.0,
VolatilityType type = ShiftedLognormal,
Real displacement = 0.0) const;
Real displacement = 0.0,
PriceType priceType = Spot) const;
private:
// arguments
ext::shared_ptr<FixedVsFloatingSwap> swap_;
Expand Down
8 changes: 6 additions & 2 deletions ql/pricingengines/swaption/blackswaptionengine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ namespace QuantLib {

// shifted lognormal type engine
struct Black76Spec {
static const VolatilityType type = ShiftedLognormal;
static constexpr VolatilityType type = ShiftedLognormal;
Real value(const Option::Type type, const Real strike,
const Real atmForward, const Real stdDev, const Real annuity,
const Real displacement) {
Expand All @@ -103,7 +103,7 @@ namespace QuantLib {

// normal type engine
struct BachelierSpec {
static const VolatilityType type = Normal;
static constexpr VolatilityType type = Normal;
Real value(const Option::Type type, const Real strike,
const Real atmForward, const Real stdDev, const Real annuity,
const Real) {
Expand Down Expand Up @@ -221,6 +221,9 @@ namespace QuantLib {
void BlackStyleSwaptionEngine<Spec>::calculate() const {
static const Spread basisPoint = 1.0e-4;

QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
"not a European option");

Date exerciseDate = arguments_.exercise->date(0);

// The part of the swap preceding exerciseDate should be truncated to avoid taking into
Expand Down Expand Up @@ -320,6 +323,7 @@ namespace QuantLib {
w, strike, atmForward, stdDev, annuity, displacement);
results_.additionalResults["timeToExpiry"] = exerciseTime;
results_.additionalResults["impliedVolatility"] = Real(stdDev / std::sqrt(exerciseTime));
results_.additionalResults["forwardPrice"] = results_.value / discountCurve_->discount(exerciseDate);
}

} // namespace detail
Expand Down
28 changes: 20 additions & 8 deletions test-suite/swaption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) {

Settlement::Type types[] = { Settlement::Physical, Settlement::Cash };
Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve };
Swaption::PriceType priceTypes[] = { Swaption::Spot, Swaption::Forward };
// test data
Rate strikes[] = { 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 };
Volatility vols[] = { 0.01, 0.05, 0.10, 0.20, 0.30, 0.70, 0.90 };
Expand All @@ -851,12 +852,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) {
.withFloatingLegSpread(0.0)
.withType(k);
for (Size h=0; h<LENGTH(types); h++) {
for (auto priceType : priceTypes) {
for (Real vol : vols) {
ext::shared_ptr<Swaption> swaption =
vars.makeSwaption(swap, exerciseDate, vol, types[h], methods[h],
BlackSwaptionEngine::DiscountCurve);
// Black price
Real value = swaption->NPV();
Real value = priceType == Swaption::Spot? swaption->NPV() : swaption->result<Real>("forwardPrice");
Volatility implVol = 0.0;
try {
implVol =
Expand All @@ -868,11 +870,12 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) {
1.0e-7,
4.0,
ShiftedLognormal,
0.0);
0.0,
priceType);
} catch (std::exception& e) {
// couldn't bracket?
swaption->setPricingEngine(vars.makeEngine(0.0, BlackSwaptionEngine::DiscountCurve));
Real value2 = swaption->NPV();
Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result<Real>("forwardPrice");
if (std::fabs(value-value2) < tolerance) {
// ok, just skip:
continue;
Expand All @@ -885,12 +888,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) {
<< "\natm level: " << io::rate(swap->fairRate())
<< "\nvol: " << io::volatility(vol)
<< "\nprice: " << value << "\n"
<< "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n"
<< e.what());
}
if (std::fabs(implVol - vol) > tolerance) {
// the difference might not matter
swaption->setPricingEngine(vars.makeEngine(implVol, BlackSwaptionEngine::DiscountCurve));
Real value2 = swaption->NPV();
Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result<Real>("forwardPrice");
if (std::fabs(value-value2) > tolerance) {
BOOST_ERROR("implied vol failure: "
<< exercise << "x" << length << " " << k
Expand All @@ -899,11 +903,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) {
<< "\natm level: " << io::rate(swap->fairRate())
<< "\nvol: " << io::volatility(vol)
<< "\nprice: " << value
<< "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n"
<< "\nimplied vol: " << io::volatility(implVol)
<< "\nimplied price: " << value2);
}
}
}
}
}
}
}
Expand All @@ -923,6 +929,7 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) {

Settlement::Type types[] = { Settlement::Physical, Settlement::Cash };
Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve };
Swaption::PriceType priceTypes[] = { Swaption::Spot, Swaption::Forward };
// test data
Rate strikes[] = { 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 };
Volatility vols[] = { 0.01, 0.05, 0.10, 0.20, 0.30, 0.70, 0.90 };
Expand All @@ -942,12 +949,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) {
.withFixedLegDayCount(vars.fixedDayCount)
.withType(k);
for (Size h=0; h<LENGTH(types); h++) {
for (auto priceType : priceTypes) {
for (Real vol : vols) {
ext::shared_ptr<Swaption> swaption =
vars.makeOISwaption(swap, exerciseDate, vol, types[h], methods[h],
BlackSwaptionEngine::DiscountCurve);
// Black price
Real value = swaption->NPV();
Real value = priceType == Swaption::Spot? swaption->NPV() : swaption->result<Real>("forwardPrice");
Volatility implVol = 0.0;
try {
implVol =
Expand All @@ -959,11 +967,12 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) {
1.0e-7,
4.0,
ShiftedLognormal,
0.0);
0.0,
priceType);
} catch (std::exception& e) {
// couldn't bracket?
swaption->setPricingEngine(vars.makeEngine(0.0, BlackSwaptionEngine::DiscountCurve));
Real value2 = swaption->NPV();
Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result<Real>("forwardPrice");
if (std::fabs(value-value2) < tolerance) {
// ok, just skip:
continue;
Expand All @@ -976,12 +985,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) {
<< "\natm level: " << io::rate(swap->fairRate())
<< "\nvol: " << io::volatility(vol)
<< "\nprice: " << value << "\n"
<< "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n"
<< e.what());
}
if (std::fabs(implVol - vol) > tolerance) {
// the difference might not matter
swaption->setPricingEngine(vars.makeEngine(implVol, BlackSwaptionEngine::DiscountCurve));
Real value2 = swaption->NPV();
Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result<Real>("forwardPrice");
if (std::fabs(value-value2) > tolerance) {
BOOST_ERROR("implied vol failure: "
<< exercise << "x" << length << " " << k
Expand All @@ -990,11 +1000,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) {
<< "\natm level: " << io::rate(swap->fairRate())
<< "\nvol: " << io::volatility(vol)
<< "\nprice: " << value
<< "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n"
<< "\nimplied vol: " << io::volatility(implVol)
<< "\nimplied price: " << value2);
}
}
}
}
}
}
}
Expand Down

0 comments on commit ee934e4

Please sign in to comment.