All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opengamma.strata.pricer.swaption.SabrSwaptionCalibrator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.swaption;

import java.time.LocalDate;
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.TreeMap;
import java.util.function.Function;

import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.basics.date.DayCount;
import com.opengamma.strata.basics.date.Tenor;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.collect.tuple.Pair;
import com.opengamma.strata.market.ValueType;
import com.opengamma.strata.market.param.ParameterMetadata;
import com.opengamma.strata.market.surface.InterpolatedNodalSurface;
import com.opengamma.strata.market.surface.Surface;
import com.opengamma.strata.market.surface.SurfaceMetadata;
import com.opengamma.strata.market.surface.Surfaces;
import com.opengamma.strata.market.surface.interpolator.SurfaceInterpolator;
import com.opengamma.strata.math.MathException;
import com.opengamma.strata.math.impl.rootfinding.NewtonRaphsonSingleRootFinder;
import com.opengamma.strata.math.impl.statistics.leastsquare.LeastSquareResultsWithTransform;
import com.opengamma.strata.pricer.impl.option.BlackFormulaRepository;
import com.opengamma.strata.pricer.impl.volatility.smile.SabrFormulaData;
import com.opengamma.strata.pricer.impl.volatility.smile.SabrModelFitter;
import com.opengamma.strata.pricer.model.SabrInterestRateParameters;
import com.opengamma.strata.pricer.model.SabrVolatilityFormula;
import com.opengamma.strata.pricer.option.RawOptionData;
import com.opengamma.strata.pricer.option.TenorRawOptionData;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer;
import com.opengamma.strata.product.common.BuySell;
import com.opengamma.strata.product.swap.SwapTrade;
import com.opengamma.strata.product.swap.type.FixedFloatSwapConvention;

/**
 * Swaption SABR calibrator.
 * 

* This calibrator takes raw data and produces calibrated SABR parameters. */ public final class SabrSwaptionCalibrator { /** * The SABR implied volatility formula. */ private final SabrVolatilityFormula sabrVolatilityFormula; /** * The swap pricer. * Required for forward rate computation. */ private final DiscountingSwapProductPricer swapPricer; /** * The reference data. */ private final ReferenceData refData; /** The root-finder used in the Alpha calibration to ATM volatility. */ private static final NewtonRaphsonSingleRootFinder ROOT_FINDER = new NewtonRaphsonSingleRootFinder(); /** * The default instance of the class. */ public static final SabrSwaptionCalibrator DEFAULT = new SabrSwaptionCalibrator( SabrVolatilityFormula.hagan(), DiscountingSwapProductPricer.DEFAULT, ReferenceData.standard()); //------------------------------------------------------------------------- /** * Obtains an instance from a SABR volatility function provider and a swap pricer. *

* The swap pricer is used to compute the forward rate required for calibration. * * @param sabrVolatilityFormula the SABR implied volatility formula * @param swapPricer the swap pricer * @return the calibrator */ public static SabrSwaptionCalibrator of( SabrVolatilityFormula sabrVolatilityFormula, DiscountingSwapProductPricer swapPricer) { return new SabrSwaptionCalibrator(sabrVolatilityFormula, swapPricer, ReferenceData.standard()); } /** * Obtains an instance from a SABR volatility function provider and a swap pricer. *

* The swap pricer is used to compute the forward rate required for calibration. * * @param sabrVolatilityFormula the SABR implied volatility formula * @param swapPricer the swap pricer * @param refData the reference data * @return the calibrator */ public static SabrSwaptionCalibrator of( SabrVolatilityFormula sabrVolatilityFormula, DiscountingSwapProductPricer swapPricer, ReferenceData refData) { return new SabrSwaptionCalibrator(sabrVolatilityFormula, swapPricer, refData); } private SabrSwaptionCalibrator( SabrVolatilityFormula sabrVolatilityFormula, DiscountingSwapProductPricer swapPricer, ReferenceData refData) { this.sabrVolatilityFormula = ArgChecker.notNull(sabrVolatilityFormula, "sabrVolatilityFormula"); this.swapPricer = ArgChecker.notNull(swapPricer, "swapPricer"); this.refData = ArgChecker.notNull(refData, "refData"); } //------------------------------------------------------------------------- /** * Calibrate SABR parameters to a set of raw swaption data. *

* The SABR parameters are calibrated with fixed beta and fixed shift surfaces. * The raw data can be (shifted) log-normal volatilities, normal volatilities or option prices * * @param definition the definition of the calibration to be performed * @param calibrationDateTime the data and time of the calibration * @param data the map of raw option data, keyed by tenor * @param ratesProvider the rate provider used to compute the swap forward rates * @param betaSurface the beta surface * @param shiftSurface the shift surface * @return the SABR volatility object */ @SuppressWarnings("null") public SabrParametersSwaptionVolatilities calibrateWithFixedBetaAndShift( SabrSwaptionDefinition definition, ZonedDateTime calibrationDateTime, TenorRawOptionData data, RatesProvider ratesProvider, Surface betaSurface, Surface shiftSurface) { // If a MathException is thrown by a calibration for a specific expiry/tenor, an exception is thrown by the method return calibrateWithFixedBetaAndShift( definition, calibrationDateTime, data, ratesProvider, betaSurface, shiftSurface, true); } //------------------------------------------------------------------------- /** * Calibrate SABR parameters to a set of raw swaption data. *

* The SABR parameters are calibrated with fixed beta and fixed shift surfaces. * The raw data can be (shifted) log-normal volatilities, normal volatilities or option prices *

* This method offers the flexibility to skip the data sets that throw a MathException (stopOnMathException = false). * The option to skip those data sets should be use with care, as part of the input data may be unused in the output. * * @param definition the definition of the calibration to be performed * @param calibrationDateTime the data and time of the calibration * @param data the map of raw option data, keyed by tenor * @param ratesProvider the rate provider used to compute the swap forward rates * @param betaSurface the beta surface * @param shiftSurface the shift surface * @param stopOnMathException flag indicating if the calibration should stop on math exceptions or skip the * expiries/tenors which throw MathException * @return the SABR volatility object */ @SuppressWarnings("null") public SabrParametersSwaptionVolatilities calibrateWithFixedBetaAndShift( SabrSwaptionDefinition definition, ZonedDateTime calibrationDateTime, TenorRawOptionData data, RatesProvider ratesProvider, Surface betaSurface, Surface shiftSurface, boolean stopOnMathException) { SwaptionVolatilitiesName name = definition.getName(); FixedFloatSwapConvention convention = definition.getConvention(); DayCount dayCount = definition.getDayCount(); SurfaceInterpolator interpolator = definition.getInterpolator(); BitSet fixed = new BitSet(); fixed.set(1); // Beta fixed BusinessDayAdjustment bda = convention.getFloatingLeg().getStartDateBusinessDayAdjustment(); LocalDate calibrationDate = calibrationDateTime.toLocalDate(); // Sorted maps to obtain the surfaces nodes in standard order TreeMap> parameterMetadataTmp = new TreeMap<>(); TreeMap> dataSensitivityAlphaTmp = new TreeMap<>(); // Sensitivity to the calibrating data TreeMap> dataSensitivityRhoTmp = new TreeMap<>(); TreeMap> dataSensitivityNuTmp = new TreeMap<>(); TreeMap> sabrPointTmp = new TreeMap<>(); for (Tenor tenor : data.getTenors()) { RawOptionData tenorData = data.getData(tenor); double timeTenor = tenor.getPeriod().getYears() + tenor.getPeriod().getMonths() / 12; List expiries = tenorData.getExpiries(); for (Period expiry : expiries) { Pair availableSmile = tenorData.availableSmileAtExpiry(expiry); if (availableSmile.getFirst().size() == 0) { // If not data is available, no calibration possible continue; } LocalDate exerciseDate = expirationDate(bda, calibrationDate, expiry); LocalDate effectiveDate = convention.calculateSpotDateFromTradeDate(exerciseDate, refData); double timeToExpiry = dayCount.relativeYearFraction(calibrationDate, exerciseDate); double beta = betaSurface.zValue(timeToExpiry, timeTenor); double shift = shiftSurface.zValue(timeToExpiry, timeTenor); LocalDate endDate = effectiveDate.plus(tenor); SwapTrade swap0 = convention.toTrade(calibrationDate, effectiveDate, endDate, BuySell.BUY, 1.0, 0.0); double forward = swapPricer.parRate(swap0.getProduct().resolve(refData), ratesProvider); SabrFormulaData sabrPoint = null; DoubleMatrix inverseJacobian = null; boolean error = false; try { Pair calibrationResult = calibration(forward, shift, beta, fixed, bda, calibrationDateTime, dayCount, availableSmile.getFirst(), availableSmile.getSecond(), expiry, tenorData); sabrPoint = calibrationResult.getFirst(); inverseJacobian = calibrationResult.getSecond(); } catch (MathException e) { error = true; if (stopOnMathException) { String message = Messages.format("{} at expiry {} and tenor {}", e.getMessage(), expiry, tenor); throw new MathException(message, e); } } if (!error) { if (!parameterMetadataTmp.containsKey(timeToExpiry)) { parameterMetadataTmp.put(timeToExpiry, new TreeMap<>()); dataSensitivityAlphaTmp.put(timeToExpiry, new TreeMap<>()); dataSensitivityRhoTmp.put(timeToExpiry, new TreeMap<>()); dataSensitivityNuTmp.put(timeToExpiry, new TreeMap<>()); sabrPointTmp.put(timeToExpiry, new TreeMap<>()); } TreeMap parameterMetadataExpiryMap = parameterMetadataTmp.get(timeToExpiry); TreeMap dataSensitivityAlphaExpiryMap = dataSensitivityAlphaTmp.get(timeToExpiry); TreeMap dataSensitivityRhoExpiryMap = dataSensitivityRhoTmp.get(timeToExpiry); TreeMap dataSensitivityNuExpiryMap = dataSensitivityNuTmp.get(timeToExpiry); TreeMap sabrPointExpiryMap = sabrPointTmp.get(timeToExpiry); parameterMetadataExpiryMap.put(timeTenor, SwaptionSurfaceExpiryTenorParameterMetadata.of( timeToExpiry, timeTenor, expiry.toString() + "x" + tenor)); dataSensitivityAlphaExpiryMap.put(timeTenor, inverseJacobian.row(0)); dataSensitivityRhoExpiryMap.put(timeTenor, inverseJacobian.row(2)); dataSensitivityNuExpiryMap.put(timeTenor, inverseJacobian.row(3)); sabrPointExpiryMap.put(timeTenor, sabrPoint); } } } DoubleArray timeToExpiryArray = DoubleArray.EMPTY; DoubleArray timeTenorArray = DoubleArray.EMPTY; DoubleArray alphaArray = DoubleArray.EMPTY; DoubleArray rhoArray = DoubleArray.EMPTY; DoubleArray nuArray = DoubleArray.EMPTY; List parameterMetadata = new ArrayList<>(); List dataSensitivityAlpha = new ArrayList<>(); // Sensitivity to the calibrating data List dataSensitivityRho = new ArrayList<>(); List dataSensitivityNu = new ArrayList<>(); for (Double timeToExpiry : parameterMetadataTmp.keySet()) { TreeMap parameterMetadataExpiryMap = parameterMetadataTmp.get(timeToExpiry); TreeMap dataSensitivityAlphaExpiryMap = dataSensitivityAlphaTmp.get(timeToExpiry); TreeMap dataSensitivityRhoExpiryMap = dataSensitivityRhoTmp.get(timeToExpiry); TreeMap dataSensitivityNuExpiryMap = dataSensitivityNuTmp.get(timeToExpiry); TreeMap sabrPointExpiryMap = sabrPointTmp.get(timeToExpiry); for (Double timeTenor : parameterMetadataExpiryMap.keySet()) { parameterMetadata.add(parameterMetadataExpiryMap.get(timeTenor)); dataSensitivityAlpha.add(dataSensitivityAlphaExpiryMap.get(timeTenor)); dataSensitivityRho.add(dataSensitivityRhoExpiryMap.get(timeTenor)); dataSensitivityNu.add(dataSensitivityNuExpiryMap.get(timeTenor)); timeToExpiryArray = timeToExpiryArray.concat(timeToExpiry); timeTenorArray = timeTenorArray.concat(timeTenor); SabrFormulaData sabrPt = sabrPointExpiryMap.get(timeTenor); alphaArray = alphaArray.concat(sabrPt.getAlpha()); rhoArray = rhoArray.concat(sabrPt.getRho()); nuArray = nuArray.concat(sabrPt.getNu()); } } SurfaceMetadata metadataAlpha = Surfaces.sabrParameterByExpiryTenor( name.getName() + "-Alpha", dayCount, ValueType.SABR_ALPHA) .withParameterMetadata(parameterMetadata); SurfaceMetadata metadataRho = Surfaces.sabrParameterByExpiryTenor( name.getName() + "-Rho", dayCount, ValueType.SABR_RHO) .withParameterMetadata(parameterMetadata); SurfaceMetadata metadataNu = Surfaces.sabrParameterByExpiryTenor( name.getName() + "-Nu", dayCount, ValueType.SABR_NU) .withParameterMetadata(parameterMetadata); InterpolatedNodalSurface alphaSurface = InterpolatedNodalSurface .of(metadataAlpha, timeToExpiryArray, timeTenorArray, alphaArray, interpolator); InterpolatedNodalSurface rhoSurface = InterpolatedNodalSurface .of(metadataRho, timeToExpiryArray, timeTenorArray, rhoArray, interpolator); InterpolatedNodalSurface nuSurface = InterpolatedNodalSurface .of(metadataNu, timeToExpiryArray, timeTenorArray, nuArray, interpolator); SabrInterestRateParameters params = SabrInterestRateParameters.of( alphaSurface, betaSurface, rhoSurface, nuSurface, shiftSurface, sabrVolatilityFormula); return SabrParametersSwaptionVolatilities.builder() .name(name) .convention(convention) .valuationDateTime(calibrationDateTime) .parameters(params) .dataSensitivityAlpha(dataSensitivityAlpha) .dataSensitivityRho(dataSensitivityRho) .dataSensitivityNu(dataSensitivityNu).build(); } // The main part of the calibration. The calibration is done 4 times with different starting points: low and high // volatilities and high and low vol of vol. The best result (in term of chi^2) is returned. private Pair calibration( double forward, double shift, double beta, BitSet fixed, BusinessDayAdjustment bda, ZonedDateTime calibrationDateTime, DayCount dayCount, DoubleArray strike, DoubleArray data, Period expiry, RawOptionData rawData) { double rhoStart = -0.50 * beta + 0.50 * (1 - beta); // Correlation is usually positive for normal and negative for log-normal;. double[] alphaStart = new double[4]; alphaStart[0] = 0.0025 / Math.pow(forward + shift, beta); // Low vol alphaStart[1] = alphaStart[0]; alphaStart[2] = 4 * alphaStart[0]; // High vol alphaStart[3] = alphaStart[2]; double[] nuStart = new double[4]; nuStart[0] = 0.10; // Low vol of vol nuStart[1] = 0.50; // High vol of vol nuStart[2] = 0.10; nuStart[3] = 0.50; double chi2 = 1.0E+12; // Large number Pair sabrCalibrationResult = null; for (int i = 0; i < 4; i++) { // Try different starting points and take the best DoubleArray startParameters = DoubleArray.of(alphaStart[i], beta, rhoStart, nuStart[i]); Pair r; if (rawData.getDataType().equals(ValueType.NORMAL_VOLATILITY)) { r = calibrateLsShiftedFromNormalVolatilities(bda, calibrationDateTime, dayCount, expiry, forward, strike, rawData.getStrikeType(), data, startParameters, fixed, shift); } else { if (rawData.getDataType().equals(ValueType.PRICE)) { r = calibrateLsShiftedFromPrices(bda, calibrationDateTime, dayCount, expiry, forward, strike, rawData.getStrikeType(), data, startParameters, fixed, shift); } else { if (rawData.getDataType().equals(ValueType.BLACK_VOLATILITY)) { r = calibrateLsShiftedFromBlackVolatilities(bda, calibrationDateTime, dayCount, expiry, forward, strike, rawData.getStrikeType(), data, rawData.getShift().orElse(0d), startParameters, fixed, shift); } else { throw new IllegalArgumentException("Data type not supported"); } } } if (r.getFirst().getChiSq() < chi2) { // Keep best calibration sabrCalibrationResult = r; chi2 = r.getFirst().getChiSq(); } } @SuppressWarnings("null") SabrFormulaData sabrParameters = SabrFormulaData.of(sabrCalibrationResult.getFirst().getModelParameters().toArrayUnsafe()); DoubleMatrix parameterSensitivityToBlackShifted = sabrCalibrationResult.getFirst().getModelParameterSensitivityToData(); DoubleArray blackVolSensitivitytoRawData = sabrCalibrationResult.getSecond(); // Multiply the sensitivity to the intermediary (shifted) log-normal vol by its sensitivity to the raw data double[][] parameterSensitivityToDataArray = new double[4][blackVolSensitivitytoRawData.size()]; for (int loopsabr = 0; loopsabr < 4; loopsabr++) { for (int loopdata = 0; loopdata < blackVolSensitivitytoRawData.size(); loopdata++) { parameterSensitivityToDataArray[loopsabr][loopdata] = parameterSensitivityToBlackShifted.get(loopsabr, loopdata) * blackVolSensitivitytoRawData.get(loopdata); } } DoubleMatrix parameterSensitivityToData = DoubleMatrix.ofUnsafe(parameterSensitivityToDataArray); return Pair.of(sabrParameters, parameterSensitivityToData); } //------------------------------------------------------------------------- /** * Calibrate SABR alpha parameters to a set of ATM swaption volatilities. *

* The SABR parameters are calibrated with all the parameters other than alpha (beta, rhu, nu, shift) fixed. * The at-the-money volatilities can be log-normal or normal volatilities. * * @param name the name * @param sabr the SABR parameters from which the beta, rho, nu and shift are extracted * @param ratesProvider the rate provider used to compute the swap forward rates * @param atmVolatilities the swaption volatilities containing the ATM volatilities to be calibrated * @param tenors the tenors for which the alpha parameter should be calibrated * @param expiries the expiries for which the alpha parameter should be calibrated * @param interpolator the interpolator for the alpha surface * @return the SABR volatility object */ public SabrParametersSwaptionVolatilities calibrateAlphaWithAtm( SwaptionVolatilitiesName name, SabrParametersSwaptionVolatilities sabr, RatesProvider ratesProvider, SwaptionVolatilities atmVolatilities, List tenors, List expiries, SurfaceInterpolator interpolator) { FixedFloatSwapConvention convention = sabr.getConvention(); DayCount dayCount = sabr.getDayCount(); BusinessDayAdjustment bda = convention.getFloatingLeg().getStartDateBusinessDayAdjustment(); LocalDate calibrationDate = sabr.getValuationDate(); DoubleArray timeToExpiryArray = DoubleArray.EMPTY; DoubleArray timeTenorArray = DoubleArray.EMPTY; DoubleArray alphaArray = DoubleArray.EMPTY; List parameterMetadata = new ArrayList<>(); List dataSensitivityAlpha = new ArrayList<>(); // Sensitivity to the calibrating data for (Period expiry : expiries) { for (Tenor tenor : tenors) { double timeTenor = tenor.getPeriod().getYears() + tenor.getPeriod().getMonths() / 12; LocalDate exerciseDate = expirationDate(bda, calibrationDate, expiry); LocalDate effectiveDate = convention.calculateSpotDateFromTradeDate(exerciseDate, refData); double timeToExpiry = dayCount.relativeYearFraction(calibrationDate, exerciseDate); LocalDate endDate = effectiveDate.plus(tenor); SwapTrade swap0 = convention.toTrade(calibrationDate, effectiveDate, endDate, BuySell.BUY, 1.0, 0.0); double forward = swapPricer.parRate(swap0.getProduct().resolve(refData), ratesProvider); double atmVolatility = atmVolatilities.volatility(timeToExpiry, timeTenor, forward, forward); ValueType volatilityType = atmVolatilities.getVolatilityType(); // Currently there is no 'SwaptionVolatilities' with Black shifted. double beta = sabr.getParameters().beta(timeToExpiry, timeTenor); double rho = sabr.getParameters().rho(timeToExpiry, timeTenor); double nu = sabr.getParameters().nu(timeToExpiry, timeTenor); double shift = sabr.getParameters().shift(timeToExpiry, timeTenor); Pair calibrationResult = calibrationAtm(forward, shift, beta, rho, nu, bda, sabr.getValuationDateTime(), dayCount, expiry, atmVolatility, volatilityType); timeToExpiryArray = timeToExpiryArray.concat(timeToExpiry); timeTenorArray = timeTenorArray.concat(timeTenor); alphaArray = alphaArray.concat(calibrationResult.getFirst()); parameterMetadata.add( SwaptionSurfaceExpiryTenorParameterMetadata.of( timeToExpiry, timeTenor, expiry.toString() + "x" + tenor)); dataSensitivityAlpha.add(DoubleArray.of(calibrationResult.getSecond())); } } SurfaceMetadata metadataAlpha = Surfaces.sabrParameterByExpiryTenor( name.getName() + "-Alpha", dayCount, ValueType.SABR_ALPHA) .withParameterMetadata(parameterMetadata); InterpolatedNodalSurface alphaSurface = InterpolatedNodalSurface .of(metadataAlpha, timeToExpiryArray, timeTenorArray, alphaArray, interpolator); SabrInterestRateParameters params = SabrInterestRateParameters.of( alphaSurface, sabr.getParameters().getBetaSurface(), sabr.getParameters().getRhoSurface(), sabr.getParameters().getNuSurface(), sabr.getParameters().getShiftSurface(), sabrVolatilityFormula); return SabrParametersSwaptionVolatilities.builder() .name(name) .convention(convention) .valuationDateTime(sabr.getValuationDateTime()) .parameters(params) .dataSensitivityAlpha(dataSensitivityAlpha).build(); } // Calibration for one option. Distribute the calculation according to the type of volatility (Black/Normal) private Pair calibrationAtm( double forward, double shift, double beta, double rho, double nu, BusinessDayAdjustment bda, ZonedDateTime calibrationDateTime, DayCount dayCount, Period expiry, double volatility, ValueType volatilityType) { double alphaStart = volatility / Math.pow(forward + shift, beta); DoubleArray startParameters = DoubleArray.of(alphaStart, beta, rho, nu); Pair r; if (volatilityType.equals(ValueType.NORMAL_VOLATILITY)) { r = calibrateAtmShiftedFromNormalVolatilities( bda, calibrationDateTime, dayCount, expiry, forward, volatility, startParameters, shift); } else { if (volatilityType.equals(ValueType.BLACK_VOLATILITY)) { r = calibrateAtmShiftedFromBlackVolatilities( bda, calibrationDateTime, dayCount, expiry, forward, volatility, 0.0, startParameters, shift); } else { throw new IllegalArgumentException("Data type not supported"); } } return r; } //------------------------------------------------------------------------- /** * Calibrate the SABR parameters to a set of Black volatilities at given moneyness by least square. *

* All the associated swaptions have the same expiration date, given by a period * from calibration time, and the same tenor. * * @param bda the business day adjustment for the exercise date adjustment * @param calibrationDateTime the calibration date and time * @param dayCount the day count for the computation of the time to exercise * @param periodToExpiry the period to expiry * @param forward the forward price/rate * @param strikesLike the options strike-like dimension * @param strikeType the strike type * @param blackVolatilitiesInput the option (call/payer) implied volatilities in shifted Black model * @param shiftInput the shift used to computed the input implied shifted Black volatilities * @param startParameters the starting parameters for the calibration. If one or more of the parameters are fixed, * the starting value will be used as the fixed parameter. * @param fixedParameters the flag for the fixed parameters that are not calibrated * @param shiftOutput the shift to calibrate the shifted SABR * @return the least square results and the derivative of the shifted log-normal used for calibration with respect * to the raw data */ public Pair calibrateLsShiftedFromBlackVolatilities( BusinessDayAdjustment bda, ZonedDateTime calibrationDateTime, DayCount dayCount, Period periodToExpiry, double forward, DoubleArray strikesLike, ValueType strikeType, DoubleArray blackVolatilitiesInput, double shiftInput, DoubleArray startParameters, BitSet fixedParameters, double shiftOutput) { int nbStrikes = strikesLike.size(); ArgChecker.isTrue(nbStrikes == blackVolatilitiesInput.size(), "size of strikes must be the same as size of volatilities"); LocalDate calibrationDate = calibrationDateTime.toLocalDate(); LocalDate exerciseDate = expirationDate(bda, calibrationDate, periodToExpiry); double timeToExpiry = dayCount.relativeYearFraction(calibrationDate, exerciseDate); DoubleArray errors = DoubleArray.filled(nbStrikes, 1e-4); DoubleArray strikes = strikesShifted(forward, 0.0, strikesLike, strikeType); Pair volAndDerivatives = blackVolatilitiesShiftedFromBlackVolatilitiesShifted( forward, shiftOutput, timeToExpiry, strikes, blackVolatilitiesInput, shiftInput); DoubleArray blackVolatilitiesTransformed = volAndDerivatives.getFirst(); DoubleArray strikesShifted = strikesShifted(forward, shiftOutput, strikesLike, strikeType); SabrModelFitter fitter = new SabrModelFitter( forward + shiftOutput, strikesShifted, timeToExpiry, blackVolatilitiesTransformed, errors, sabrVolatilityFormula); LeastSquareResultsWithTransform result = fitter.solve(startParameters, fixedParameters); return Pair.of(result, volAndDerivatives.getSecond()); } //------------------------------------------------------------------------- /** * Calibrate the SABR alpha parameter to an ATM Black volatility and compute the derivative of the result with * respect to the input volatility. * * @param bda the business day adjustment for the exercise date adjustment * @param calibrationDateTime the calibration date and time * @param dayCount the day count for the computation of the time to exercise * @param periodToExpiry the period to expiry * @param forward the forward price/rate * @param blackVolatility the option (call/payer) Black implied volatility * @param shiftInput the shift used to computed the input implied shifted Black volatilities * @param startParameters the starting parameters for the calibration. The alpha parameter is used as a starting * point for the root-finding, the other parameters are fixed. * @param shiftOutput the shift to calibrate the shifted SABR * @return the alpha calibrated and its derivative with respect to the volatility */ public Pair calibrateAtmShiftedFromBlackVolatilities( BusinessDayAdjustment bda, ZonedDateTime calibrationDateTime, DayCount dayCount, Period periodToExpiry, double forward, double blackVolatility, double shiftInput, DoubleArray startParameters, double shiftOutput) { LocalDate calibrationDate = calibrationDateTime.toLocalDate(); LocalDate exerciseDate = expirationDate(bda, calibrationDate, periodToExpiry); double timeToExpiry = dayCount.relativeYearFraction(calibrationDate, exerciseDate); Pair volAndDerivatives = blackVolatilitiesShiftedFromBlackVolatilitiesShifted( forward, shiftOutput, timeToExpiry, DoubleArray.of(forward), DoubleArray.of(blackVolatility), shiftInput); DoubleArray blackVolatilitiesTransformed = volAndDerivatives.getFirst(); Function volFunction = (a) -> sabrVolatilityFormula.volatility(forward + shiftOutput, forward + shiftOutput, timeToExpiry, a, startParameters.get(1), startParameters.get(2), startParameters.get(3)) - blackVolatilitiesTransformed.get(0); double alphaCalibrated = ROOT_FINDER.getRoot(volFunction, startParameters.get(0)); double dAlphadBlack = 1.0d / sabrVolatilityFormula.volatilityAdjoint(forward + shiftOutput, forward + shiftOutput, timeToExpiry, alphaCalibrated, startParameters.get(1), startParameters.get(2), startParameters.get(3)) .getDerivative(2); return Pair.of(alphaCalibrated, dAlphadBlack * volAndDerivatives.getSecond().get(0)); } //------------------------------------------------------------------------- /** * Creates an array of shifted Black volatilities from shifted Black volatilities with a different shift and * the sensitivities of the Black volatilities outputs with respect to the normal volatilities inputs. * * @param forward the forward rate * @param shiftOutput the shift required in the output * @param timeToExpiry the time to expiration * @param strikes the option strikes * @param blackVolatilities the shifted implied Black volatilities * @param shiftInput the shift used in the input Black implied volatilities * @return the shifted black volatilities and their derivatives */ public Pair blackVolatilitiesShiftedFromBlackVolatilitiesShifted( double forward, double shiftOutput, double timeToExpiry, DoubleArray strikes, DoubleArray blackVolatilities, double shiftInput) { if (shiftInput == shiftOutput) { return Pair.of(blackVolatilities, DoubleArray.filled(blackVolatilities.size(), 1.0d)); // No change required if shifts are the same } int nbStrikes = strikes.size(); double[] impliedVolatility = new double[nbStrikes]; double[] impliedVolatilityDerivatives = new double[nbStrikes]; for (int i = 0; i < nbStrikes; i++) { ValueDerivatives price = BlackFormulaRepository.priceAdjoint( forward + shiftInput, strikes.get(i) + shiftInput, timeToExpiry, blackVolatilities.get(i), true); // vega-[3] ValueDerivatives iv = BlackFormulaRepository.impliedVolatilityAdjoint( price.getValue(), forward + shiftOutput, strikes.get(i) + shiftOutput, timeToExpiry, true); impliedVolatility[i] = iv.getValue(); impliedVolatilityDerivatives[i] = iv.getDerivative(0) * price.getDerivative(3); } return Pair.of(DoubleArray.ofUnsafe(impliedVolatility), DoubleArray.ofUnsafe(impliedVolatilityDerivatives)); } //------------------------------------------------------------------------- /** * Calibrate the SABR parameters to a set of option prices at given moneyness. *

* All the associated swaptions have the same expiration date, given by a period * from calibration time, and the same tenor. * * @param bda the business day adjustment for the exercise date adjustment * @param calibrationDateTime the calibration date and time * @param dayCount the day count for the computation of the time to exercise * @param periodToExpiry the period to expiry * @param forward the forward price/rate * @param strikesLike the options strike-like dimension * @param strikeType the strike type * @param prices the option (call/payer) prices * @param startParameters the starting parameters for the calibration. If one or more of the parameters are fixed, * the starting value will be used as the fixed parameter. * @param fixedParameters the flag for the fixed parameters that are not calibrated * @param shiftOutput the shift to calibrate the shifted SABR * @return SABR parameters */ public Pair calibrateLsShiftedFromPrices( BusinessDayAdjustment bda, ZonedDateTime calibrationDateTime, DayCount dayCount, Period periodToExpiry, double forward, DoubleArray strikesLike, ValueType strikeType, DoubleArray prices, DoubleArray startParameters, BitSet fixedParameters, double shiftOutput) { int nbStrikes = strikesLike.size(); ArgChecker.isTrue(nbStrikes == prices.size(), "size of strikes must be the same as size of prices"); LocalDate calibrationDate = calibrationDateTime.toLocalDate(); LocalDate exerciseDate = expirationDate(bda, calibrationDate, periodToExpiry); double timeToExpiry = dayCount.relativeYearFraction(calibrationDate, exerciseDate); DoubleArray errors = DoubleArray.filled(nbStrikes, 1e-4); DoubleArray strikes = strikesShifted(forward, 0.0, strikesLike, strikeType); Pair volAndDerivatives = blackVolatilitiesShiftedFromPrices( forward, shiftOutput, timeToExpiry, strikes, prices); DoubleArray blackVolatilitiesTransformed = volAndDerivatives.getFirst(); DoubleArray strikesShifted = strikesShifted(forward, shiftOutput, strikesLike, strikeType); SabrModelFitter fitter = new SabrModelFitter( forward + shiftOutput, strikesShifted, timeToExpiry, blackVolatilitiesTransformed, errors, sabrVolatilityFormula); return Pair.of(fitter.solve(startParameters, fixedParameters), volAndDerivatives.getSecond()); } //------------------------------------------------------------------------- /** * Creates an array of shifted Black volatilities from option prices and the sensitivities of the * Black volatilities with respect to the price inputs. * * @param forward the forward rate * @param shiftOutput the shift required in the output * @param timeToExpiry the time to expiration * @param strikes the option strikes * @param prices the option prices * @return the shifted black volatilities and their derivatives */ public Pair blackVolatilitiesShiftedFromPrices( double forward, double shiftOutput, double timeToExpiry, DoubleArray strikes, DoubleArray prices) { int nbStrikes = strikes.size(); double[] impliedVolatility = new double[nbStrikes]; double[] impliedVolatilityDerivatives = new double[nbStrikes]; for (int i = 0; i < nbStrikes; i++) { ValueDerivatives iv = BlackFormulaRepository.impliedVolatilityAdjoint( prices.get(i), forward + shiftOutput, strikes.get(i) + shiftOutput, timeToExpiry, true); impliedVolatility[i] = iv.getValue(); impliedVolatilityDerivatives[i] = iv.getDerivative(0); } return Pair.of(DoubleArray.ofUnsafe(impliedVolatility), DoubleArray.ofUnsafe(impliedVolatilityDerivatives)); } //------------------------------------------------------------------------- /** * Calibrate the SABR parameters to a set of normal volatilities at given moneyness. *

* All the associated swaptions have the same expiration date, given by a period * from calibration time, and the same tenor. * * @param bda the business day adjustment for the exercise date adjustment * @param calibrationDateTime the calibration date and time * @param dayCount the day count for the computation of the time to exercise * @param periodToExpiry the period to expiry * @param forward the forward price/rate * @param strikesLike the options strike-like dimension * @param strikeType the strike type * @param normalVolatilities the option (call/payer) normal model implied volatilities * @param startParameters the starting parameters for the calibration. If one or more of the parameters are fixed, * the starting value will be used as the fixed parameter. * @param fixedParameters the flag for the fixed parameters that are not calibrated * @param shiftOutput the shift to calibrate the shifted SABR * @return the least square results and the derivative of the shifted log-normal used for calibration with respect * to the raw data */ public Pair calibrateLsShiftedFromNormalVolatilities( BusinessDayAdjustment bda, ZonedDateTime calibrationDateTime, DayCount dayCount, Period periodToExpiry, double forward, DoubleArray strikesLike, ValueType strikeType, DoubleArray normalVolatilities, DoubleArray startParameters, BitSet fixedParameters, double shiftOutput) { int nbStrikes = strikesLike.size(); ArgChecker.isTrue(nbStrikes == normalVolatilities.size(), "size of strikes must be the same as size of prices"); LocalDate calibrationDate = calibrationDateTime.toLocalDate(); LocalDate exerciseDate = expirationDate(bda, calibrationDate, periodToExpiry); double timeToExpiry = dayCount.relativeYearFraction(calibrationDate, exerciseDate); DoubleArray errors = DoubleArray.filled(nbStrikes, 1e-4); DoubleArray strikes = strikesShifted(forward, 0.0, strikesLike, strikeType); Pair volAndDerivatives = blackVolatilitiesShiftedFromNormalVolatilities( forward, shiftOutput, timeToExpiry, strikes, normalVolatilities); DoubleArray blackVolatilitiesTransformed = volAndDerivatives.getFirst(); DoubleArray strikesShifted = strikesShifted(forward, shiftOutput, strikesLike, strikeType); SabrModelFitter fitter = new SabrModelFitter( forward + shiftOutput, strikesShifted, timeToExpiry, blackVolatilitiesTransformed, errors, sabrVolatilityFormula); LeastSquareResultsWithTransform result = fitter.solve(startParameters, fixedParameters); return Pair.of(result, volAndDerivatives.getSecond()); } //------------------------------------------------------------------------- /** * Calibrate the SABR alpha parameter to an ATM normal volatility and compute the derivative of the result * with respect to the input volatility. * * @param bda the business day adjustment for the exercise date adjustment * @param calibrationDateTime the calibration date and time * @param dayCount the day count for the computation of the time to exercise * @param periodToExpiry the period to expiry * @param forward the forward price/rate * @param normalVolatility the option (call/payer) normal model implied volatility * @param startParameters the starting parameters for the calibration. The alpha parameter is used as a starting * point for the root-finding, the other parameters are fixed. * @param shiftOutput the shift to calibrate the shifted SABR * @return the alpha calibrated and its derivative with respect to the volatility */ public Pair calibrateAtmShiftedFromNormalVolatilities( BusinessDayAdjustment bda, ZonedDateTime calibrationDateTime, DayCount dayCount, Period periodToExpiry, double forward, double normalVolatility, DoubleArray startParameters, double shiftOutput) { LocalDate calibrationDate = calibrationDateTime.toLocalDate(); LocalDate exerciseDate = expirationDate(bda, calibrationDate, periodToExpiry); double timeToExpiry = dayCount.relativeYearFraction(calibrationDate, exerciseDate); Pair volAndDerivatives = blackVolatilitiesShiftedFromNormalVolatilities( forward, shiftOutput, timeToExpiry, DoubleArray.of(forward), DoubleArray.of(normalVolatility)); DoubleArray blackVolatilitiesTransformed = volAndDerivatives.getFirst(); Function volFunction = (a) -> sabrVolatilityFormula.volatility(forward + shiftOutput, forward + shiftOutput, timeToExpiry, a, startParameters.get(1), startParameters.get(2), startParameters.get(3)) - blackVolatilitiesTransformed.get(0); double alphaCalibrated = ROOT_FINDER.getRoot(volFunction, startParameters.get(0)); double dAlphadBlack = 1.0d / sabrVolatilityFormula.volatilityAdjoint(forward + shiftOutput, forward + shiftOutput, timeToExpiry, alphaCalibrated, startParameters.get(1), startParameters.get(2), startParameters.get(3)) .getDerivative(2); return Pair.of(alphaCalibrated, dAlphadBlack * volAndDerivatives.getSecond().get(0)); } //------------------------------------------------------------------------- /** * Creates an array of shifted Black volatilities from Normal volatilities and the sensitivities of the * Black volatilities with respect to the normal volatilities inputs. *

* The transformation between normal and Black volatility is done using * {@link BlackFormulaRepository#impliedVolatilityFromNormalApproximated}. * * @param forward the forward rate * @param shiftOutput the shift required in the output * @param timeToExpiry the time to expiration * @param strikes the option strikes * @param normalVolatilities the normal volatilities * @return the shifted black volatilities and their derivatives */ public Pair blackVolatilitiesShiftedFromNormalVolatilities( double forward, double shiftOutput, double timeToExpiry, DoubleArray strikes, DoubleArray normalVolatilities) { int nbStrikes = strikes.size(); double[] impliedVolatility = new double[nbStrikes]; double[] impliedVolatilityDerivatives = new double[nbStrikes]; for (int i = 0; i < nbStrikes; i++) { ValueDerivatives iv = BlackFormulaRepository.impliedVolatilityFromNormalApproximatedAdjoint( forward + shiftOutput, strikes.get(i) + shiftOutput, timeToExpiry, normalVolatilities.get(i)); impliedVolatility[i] = iv.getValue(); impliedVolatilityDerivatives[i] = iv.getDerivative(0); } return Pair.of(DoubleArray.ofUnsafe(impliedVolatility), DoubleArray.ofUnsafe(impliedVolatilityDerivatives)); } //------------------------------------------------------------------------- /** * Compute shifted strikes from forward and strike-like value type. * * @param forward the forward rate * @param shiftOutput the shift for the output * @param strikesLike the strike-like values * @param strikeType the strike type * @return the strikes */ private DoubleArray strikesShifted(double forward, double shiftOutput, DoubleArray strikesLike, ValueType strikeType) { int nbStrikes = strikesLike.size(); if (strikeType.equals(ValueType.STRIKE)) { return DoubleArray.of(nbStrikes, i -> strikesLike.get(i) + shiftOutput); } if (strikeType.equals(ValueType.SIMPLE_MONEYNESS)) { return DoubleArray.of(nbStrikes, i -> forward + strikesLike.get(i) + shiftOutput); } if (strikeType.equals(ValueType.LOG_MONEYNESS)) { return DoubleArray.of(nbStrikes, i -> forward * Math.exp(strikesLike.get(i)) + shiftOutput); } throw new IllegalArgumentException("Strike type not supported"); } /** * Calculates the expiration date of a swaption from the calibration date and the underlying swap convention. * * @param bda the business day convention * @param calibrationDate the calibration date * @param expiry the period to expiry * @return the date */ private LocalDate expirationDate(BusinessDayAdjustment bda, LocalDate calibrationDate, Period expiry) { return bda.adjust(calibrationDate.plus(expiry), refData); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy