/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.volatilityswap; import java.util.LinkedHashMap; import com.google.common.primitives.Doubles; import com.opengamma.analytics.financial.forex.method.FXMatrix; import com.opengamma.analytics.financial.instrument.index.IborIndex; import com.opengamma.analytics.financial.instrument.index.IndexON; import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve; import com.opengamma.analytics.financial.model.volatility.surface.SmileDeltaTermStructureParameters; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderInterface; import com.opengamma.analytics.financial.provider.description.volatilityswap.CarrLeeFXData; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.Triple; /** * Compute delta, vega and theta by using finite difference method */ public class VolatilitySwapFiniteDifferenceGreeksCalculator { private static final double DEFAULT_BUMP = 1.0e-5; private static final CarrLeeNewlyIssuedSyntheticVolatilitySwapCalculator NEW_CALCULATOR = new CarrLeeNewlyIssuedSyntheticVolatilitySwapCalculator(); private static final CarrLeeSeasonedSyntheticVolatilitySwapCalculator SEASONED_CALCULATOR = new CarrLeeSeasonedSyntheticVolatilitySwapCalculator(); private final CarrLeeFXVolatilitySwapCalculator _combinedCal; private final double _bumpSpot; private final double _bumpVol; /** * Constructor using default bump amount */ public VolatilitySwapFiniteDifferenceGreeksCalculator() { this(DEFAULT_BUMP); } /** * Constructor specifying bump amount * @param bump The bump amount */ public VolatilitySwapFiniteDifferenceGreeksCalculator(final double bump) { _bumpSpot = bump; _bumpVol = _bumpSpot * 1.0e-2; _combinedCal = new CarrLeeFXVolatilitySwapCalculator(); } /** * Constructor specifying bump amount and base calculator * @param bump The bump amount * @param baseCal Base calculator */ public VolatilitySwapFiniteDifferenceGreeksCalculator(final double bump, final CarrLeeFXVolatilitySwapCalculator baseCal) { _bumpSpot = bump; _bumpVol = _bumpSpot * 1.0e-2; _combinedCal = baseCal; } /** * Greeks calculator for FX volatility swap based on "bump and reprice" using {@link VolatilitySwapCalculatorResultWithStrikes}, * i.e., assuming the fair value has been already calculated. For theta the bump amount is 1 working day. * @param result {@link VolatilitySwapCalculatorResultWithStrikes} * @param swap The FX volatility swap * @param data The FX data for Carr-Lee * @return Array of {delta, vega, theta} */ public double[] getFXVolatilitySwapGreeks(final VolatilitySwapCalculatorResultWithStrikes result, final FXVolatilitySwap swap, final CarrLeeFXData data) { ArgumentChecker.notNull(result, "result"); ArgumentChecker.notNull(swap, "swap"); ArgumentChecker.notNull(data, "data"); final double spot = data.getSpot(); final double timeToExpiry = swap.getTimeToMaturity(); ArgumentChecker.isTrue(Doubles.isFinite(timeToExpiry), "timeToExpiry should be finite"); ArgumentChecker.isTrue(timeToExpiry > 0., "timeToExpiry should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot), "spot should be finite"); ArgumentChecker.isTrue(spot > 0., "spot should be positive"); final double domesticDF = data.getMulticurveProvider().getDiscountFactor(swap.getBaseCurrency(), timeToExpiry); final double foreignDF = data.getMulticurveProvider().getDiscountFactor(swap.getCounterCurrency(), timeToExpiry); final double domesticRate = -Math.log(domesticDF) / timeToExpiry; final double foreignRate = -Math.log(foreignDF) / timeToExpiry; ArgumentChecker.isTrue(Doubles.isFinite(domesticRate), "domestic rate should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(foreignRate), "foreign rate should be finite"); final double[] putStrikes = result.getPutStrikes(); final double forward = spot * Math.exp((domesticRate - foreignRate) * timeToExpiry); final double[] callStrikes = result.getCallStrikes(); final int nPuts = putStrikes.length; final int nCalls = callStrikes.length; final SmileDeltaTermStructureParameters smile = data.getVolatilityData(); final double[] putVols = new double[nPuts]; final double[] callVols = new double[nCalls]; final double[] bumpedPutVols = new double[nPuts]; final double[] bumpedCallVols = new double[nCalls]; for (int i = 0; i < nPuts; ++i) { putVols[i] = smile.getVolatility(Triple.of(timeToExpiry, putStrikes[i], forward)); bumpedPutVols[i] = putVols[i] + _bumpVol; } for (int i = 0; i < nCalls; ++i) { callVols[i] = smile.getVolatility(Triple.of(timeToExpiry, callStrikes[i], forward)); bumpedCallVols[i] = callVols[i] + _bumpVol; } final double[] res = new double[3]; final double baseFV = result.getFairValue(); final double volBumpedFV; final Double rv = data.getRealizedVariance(); final double aFac = swap.getAnnualizationFactor(); final double timeBumpAmount = 1.0 / aFac; final double bumpedTimeToObservationStart = swap.getTimeToObservationStart() == 0. ? 0. : swap.getTimeToObservationStart() - timeBumpAmount; final FXVolatilitySwap timeBumpedSwap = new FXVolatilitySwap(bumpedTimeToObservationStart, swap.getTimeToObservationEnd() - timeBumpAmount, swap.getObservationFrequency(), swap.getTimeToMaturity() - timeBumpAmount, swap.getVolatilityStrike(), swap.getVolatilityNotional(), swap.getCurrency(), swap.getBaseCurrency(), swap.getCounterCurrency(), aFac); final VolatilitySwapCalculatorResult timeBumpedRes = _combinedCal.visitFXVolatilitySwap(timeBumpedSwap, data); final double timeBumpedFV = timeBumpedRes.getFairValue(); final CarrLeeFXData spotBumpedData = getSpotBumpedData(data); final VolatilitySwapCalculatorResult spotBumpedRes = _combinedCal.visitFXVolatilitySwap(swap, spotBumpedData); final double spotBumpedFV = spotBumpedRes.getFairValue(); if (rv == null) { final double stdVol = smile.getVolatility(Triple.of(timeToExpiry, forward, forward)); final double bumpedStdVol = stdVol + _bumpVol; final VolatilitySwapCalculatorResult volBumpedRes = NEW_CALCULATOR.evaluate(spot, putStrikes, callStrikes, timeToExpiry, domesticRate, foreignRate, bumpedPutVols, bumpedStdVol, bumpedCallVols); volBumpedFV = volBumpedRes.getFairValue(); } else { final double timeFromInception = swap.getTimeToObservationStart() < 0 ? Math.abs(swap.getTimeToObservationStart()) : 0; final VolatilitySwapCalculatorResult volBumpedRes = SEASONED_CALCULATOR.evaluate(spot, putStrikes, callStrikes, timeToExpiry, timeFromInception, domesticRate, foreignRate, bumpedPutVols, bumpedCallVols, rv); volBumpedFV = volBumpedRes.getFairValue(); } res[0] = (spotBumpedFV - baseFV) / _bumpSpot; res[1] = (volBumpedFV - baseFV) / _bumpVol * 1.0e-2; res[2] = timeBumpedFV - baseFV; return res; } /** * Greeks calculator for FX volatility swap based on "bump and reprice." * For theta the bump amount is 1 working day. * @param swap The FX volatility swap * @param data The FX data for Carr-Lee * @return Array of {delta, vega, theta} */ public double[] getFXVolatilitySwapGreeks(final FXVolatilitySwap swap, final CarrLeeFXData data) { ArgumentChecker.notNull(swap, "swap"); ArgumentChecker.notNull(data, "data"); final VolatilitySwapCalculatorResultWithStrikes result = _combinedCal.visitFXVolatilitySwap(swap, data); return getFXVolatilitySwapGreeks(result, swap, data); } private CarrLeeFXData getSpotBumpedData(final CarrLeeFXData data) { final FXMatrix spotBumpedfxMatrix = new FXMatrix(data.getCurrencyPair().getFirst(), data.getCurrencyPair().getSecond(), data.getSpot() + _bumpSpot); final MulticurveProviderInterface provider = data.getMulticurveProvider(); final MulticurveProviderDiscount spotBumpedCurves; if (provider instanceof MulticurveProviderDiscount) { final MulticurveProviderDiscount discountCurves = (MulticurveProviderDiscount) data.getMulticurveProvider(); spotBumpedCurves = new MulticurveProviderDiscount(discountCurves.getDiscountingCurves(), new LinkedHashMap<IborIndex, YieldAndDiscountCurve>(), new LinkedHashMap<IndexON, YieldAndDiscountCurve>(), spotBumpedfxMatrix); } else { throw new IllegalArgumentException("Multi-curve provider should be an instance of MulticurveProviderDiscount"); } if (data.getRealizedVariance() == null) { return new CarrLeeFXData(data.getCurrencyPair(), data.getVolatilityData(), spotBumpedCurves); } return new CarrLeeFXData(data.getCurrencyPair(), data.getVolatilityData(), spotBumpedCurves, data.getRealizedVariance()); } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(_bumpSpot); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(_bumpVol); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + ((_combinedCal == null) ? 0 : _combinedCal.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof VolatilitySwapFiniteDifferenceGreeksCalculator)) { return false; } VolatilitySwapFiniteDifferenceGreeksCalculator other = (VolatilitySwapFiniteDifferenceGreeksCalculator) obj; if (Double.doubleToLongBits(_bumpSpot) != Double.doubleToLongBits(other._bumpSpot)) { return false; } if (Double.doubleToLongBits(_bumpVol) != Double.doubleToLongBits(other._bumpVol)) { return false; } if (_combinedCal == null) { if (other._combinedCal != null) { return false; } } else if (!_combinedCal.equals(other._combinedCal)) { return false; } return true; } }