/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate.swaption.provider; import java.util.HashMap; import java.util.Map; import com.opengamma.analytics.financial.instrument.index.GeneratorSwapFixedIbor; import com.opengamma.analytics.financial.interestrate.sensitivity.PresentValueSwaptionSurfaceSensitivity; import com.opengamma.analytics.financial.interestrate.swap.provider.SwapFixedCouponDiscountingMethod; import com.opengamma.analytics.financial.interestrate.swaption.derivative.SwaptionPhysicalFixedIbor; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.NormalFunctionData; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.NormalPriceFunction; import com.opengamma.analytics.financial.provider.calculator.discounting.ParRateCurveSensitivityDiscountingCalculator; import com.opengamma.analytics.financial.provider.calculator.discounting.ParRateDiscountingCalculator; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderInterface; import com.opengamma.analytics.financial.provider.description.interestrate.NormalSwaptionProviderInterface; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MulticurveSensitivity; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.daycount.DayCount; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.MultipleCurrencyAmount; import com.opengamma.util.tuple.DoublesPair; /** * Class used to compute the price and sensitivity of a physical delivery swaption with normal (Bachelier) model. * The implied normal volatilities are expiry and underlying maturity dependent (no smile). * The swap underlying the swaption should be a Fixed for Ibor (without spread) swap. */ public final class SwaptionPhysicalFixedIborNormalMethod { /** * The method unique instance. */ private static final SwaptionPhysicalFixedIborNormalMethod INSTANCE = new SwaptionPhysicalFixedIborNormalMethod(); /** * Return the unique instance of the class. * @return The instance. */ public static SwaptionPhysicalFixedIborNormalMethod getInstance() { return INSTANCE; } /** * Private constructor. */ private SwaptionPhysicalFixedIborNormalMethod() { } /** * The calculator and methods. */ private static final ParRateDiscountingCalculator PRDC = ParRateDiscountingCalculator.getInstance(); private static final ParRateCurveSensitivityDiscountingCalculator PRCSDC = ParRateCurveSensitivityDiscountingCalculator.getInstance(); private static final SwapFixedCouponDiscountingMethod METHOD_SWAP = SwapFixedCouponDiscountingMethod.getInstance(); /** * Computes the present value of a physical delivery European swaption in the normal model. * @param swaption The swaption. * @param multicurveParameters Normal volatility for swaption and multi-curve provider. * @return The present value. */ public MultipleCurrencyAmount presentValue(final SwaptionPhysicalFixedIbor swaption, final NormalSwaptionProviderInterface multicurveParameters) { ArgumentChecker.notNull(swaption, "Swaption"); ArgumentChecker.notNull(multicurveParameters, "normal volatility for swaption and multicurve"); MulticurveProviderInterface multicurve = multicurveParameters.getMulticurveProvider(); GeneratorSwapFixedIbor generatorSwap = multicurveParameters.getGeneratorSwap(); DayCount dayCountModification = generatorSwap.getFixedLegDayCount(); Calendar calendar = generatorSwap.getCalendar(); double pvbpModified = METHOD_SWAP.presentValueBasisPoint(swaption.getUnderlyingSwap(), dayCountModification, calendar, multicurve); double forwardModified = PRDC.visitFixedCouponSwap(swaption.getUnderlyingSwap(), dayCountModification, multicurve); double strikeModified = METHOD_SWAP.couponEquivalent(swaption.getUnderlyingSwap(), pvbpModified, multicurve); double expiry = swaption.getTimeToExpiry(); double volatility = multicurveParameters.getVolatility(expiry, swaption.getMaturityTime(), strikeModified, forwardModified); NormalFunctionData normalData = new NormalFunctionData(forwardModified, pvbpModified, volatility); EuropeanVanillaOption option = new EuropeanVanillaOption(strikeModified, expiry, swaption.isCall()); // Implementation note: option required to pass the strike (in case the swap has non-constant coupon). NormalPriceFunction normalFunction = new NormalPriceFunction(); Function1D<NormalFunctionData, Double> func = normalFunction.getPriceFunction(option); double pv = func.evaluate(normalData) * (swaption.isLong() ? 1.0 : -1.0); return MultipleCurrencyAmount.of(swaption.getCurrency(), pv); } /** * Computes the implied Black volatility of the vanilla swaption. * @param swaption The swaption. * @param multicurveParameters Normal volatility for swaption and multi-curve provider. * @return The implied volatility. */ public double impliedVolatility(final SwaptionPhysicalFixedIbor swaption, final NormalSwaptionProviderInterface multicurveParameters) { ArgumentChecker.notNull(swaption, "Swaption"); ArgumentChecker.notNull(multicurveParameters, "normal volatility for swaption and multicurve"); MulticurveProviderInterface multicurve = multicurveParameters.getMulticurveProvider(); GeneratorSwapFixedIbor generatorSwap = multicurveParameters.getGeneratorSwap(); DayCount dayCountModification = generatorSwap.getFixedLegDayCount(); Calendar calendar = generatorSwap.getCalendar(); double pvbpModified = METHOD_SWAP.presentValueBasisPoint(swaption.getUnderlyingSwap(), dayCountModification, calendar, multicurve); double forwardModified = PRDC.visitFixedCouponSwap(swaption.getUnderlyingSwap(), dayCountModification, multicurve); double strikeModified = METHOD_SWAP.couponEquivalent(swaption.getUnderlyingSwap(), pvbpModified, multicurve); double expiry = swaption.getTimeToExpiry(); double volatility = multicurveParameters.getVolatility(expiry, swaption.getMaturityTime(), strikeModified, forwardModified); return volatility; } /** * Computes the present value rate sensitivity to rates of a physical delivery European swaption in the SABR model. * @param swaption The swaption. * @param multicurveParameters Normal volatility for swaption and multi-curve provider. * @return The present value curve sensitivity. */ public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(final SwaptionPhysicalFixedIbor swaption, final NormalSwaptionProviderInterface multicurveParameters) { ArgumentChecker.notNull(swaption, "Swaption"); ArgumentChecker.notNull(multicurveParameters, "normal volatility for swaption and multicurve"); MulticurveProviderInterface multicurve = multicurveParameters.getMulticurveProvider(); GeneratorSwapFixedIbor generatorSwap = multicurveParameters.getGeneratorSwap(); DayCount dayCountModification = generatorSwap.getFixedLegDayCount(); Calendar calendar = generatorSwap.getCalendar(); double pvbpModified = METHOD_SWAP.presentValueBasisPoint(swaption.getUnderlyingSwap(), dayCountModification, calendar, multicurve); double forwardModified = PRDC.visitFixedCouponSwap(swaption.getUnderlyingSwap(), dayCountModification, multicurve); double strikeModified = METHOD_SWAP.couponEquivalent(swaption.getUnderlyingSwap(), pvbpModified, multicurve); double expiry = swaption.getTimeToExpiry(); double volatility = multicurveParameters.getVolatility(expiry, swaption.getMaturityTime(), strikeModified, forwardModified); NormalFunctionData normalData = new NormalFunctionData(forwardModified, 1.0, volatility); EuropeanVanillaOption option = new EuropeanVanillaOption(strikeModified, expiry, swaption.isCall()); // Strictly speaking, the strike equivalent is curve dependent; that dependency is ignored. // Option required to pass the strike (in case the swap has non-constant coupon). NormalPriceFunction normalFunction = new NormalPriceFunction(); // Derivative of the forward and pvbp with respect to the rates. MulticurveSensitivity forwardModifiedDr = PRCSDC.visitFixedCouponSwap(swaption.getUnderlyingSwap(), dayCountModification, multicurve); MulticurveSensitivity pvbpModifiedDr = METHOD_SWAP.presentValueBasisPointCurveSensitivity( swaption.getUnderlyingSwap(), dayCountModification, calendar, multicurve); double[] normAdjoint = new double[3]; double pv = normalFunction.getPriceAdjoint(option, normalData, normAdjoint); MulticurveSensitivity result = pvbpModifiedDr.multipliedBy(pv); result = result.plus(forwardModifiedDr.multipliedBy(pvbpModified * normAdjoint[0])); if (!swaption.isLong()) { result = result.multipliedBy(-1); } return MultipleCurrencyMulticurveSensitivity.of(swaption.getCurrency(), result); } /** * Computes the present value sensitivity to the normal volatility (also called vega) of a physical delivery * European swaption in the normal swaption model. * @param swaption The swaption. * @param multicurveParameters Normal volatility for swaption and multi-curve provider. * @return The present value sensitivity to normal volatility. */ public PresentValueSwaptionSurfaceSensitivity presentValueVolatilitySensitivity(final SwaptionPhysicalFixedIbor swaption, final NormalSwaptionProviderInterface multicurveParameters) { ArgumentChecker.notNull(swaption, "Swaption"); ArgumentChecker.notNull(multicurveParameters, "normal volatility for swaption and multicurve"); MulticurveProviderInterface multicurve = multicurveParameters.getMulticurveProvider(); GeneratorSwapFixedIbor generatorSwap = multicurveParameters.getGeneratorSwap(); DayCount dayCountModification = generatorSwap.getFixedLegDayCount(); Calendar calendar = generatorSwap.getCalendar(); final double pvbpModified = METHOD_SWAP.presentValueBasisPoint(swaption.getUnderlyingSwap(), dayCountModification, calendar, multicurve); double forwardModified = PRDC.visitFixedCouponSwap(swaption.getUnderlyingSwap(), dayCountModification, multicurve); double strikeModified = METHOD_SWAP.couponEquivalent(swaption.getUnderlyingSwap(), pvbpModified, multicurve); double expiry = swaption.getTimeToExpiry(); double tenor = swaption.getMaturityTime(); double volatility = multicurveParameters.getVolatility(expiry, tenor, strikeModified, forwardModified); NormalFunctionData normalData = new NormalFunctionData(forwardModified, 1.0, volatility); EuropeanVanillaOption option = new EuropeanVanillaOption(strikeModified, expiry, swaption.isCall()); // Option required to pass the strike (in case the swap has non-constant coupon). NormalPriceFunction normalFunction = new NormalPriceFunction(); double[] normAdjoint = new double[3]; normalFunction.getPriceAdjoint(option, normalData, normAdjoint); DoublesPair point = DoublesPair.of(expiry, tenor); Map<DoublesPair, Double> sensitivity = new HashMap<>(); sensitivity.put(point, normAdjoint[1] * pvbpModified * (swaption.isLong() ? 1.0 : -1.0)); return new PresentValueSwaptionSurfaceSensitivity(sensitivity, generatorSwap); } }