CorrelationProviderTenorBasis.java

package net.finmath.marketdata.model.volatility.caplet.tenorconversion;

import java.time.LocalDate;

import net.finmath.exception.CalculationException;
import net.finmath.marketdata.model.AnalyticModel;
import net.finmath.marketdata.model.volatility.caplet.CapVolMarketData;
import net.finmath.marketdata.model.volatility.caplet.CapletVolBootstrapping;
import net.finmath.marketdata.model.volatility.caplet.smile.LinearSmileInterpolater;
import net.finmath.marketdata.model.volatility.caplet.smile.SmileInterpolationExtrapolationMethod;
import net.finmath.marketdata.products.Swap;
import net.finmath.rootfinder.NewtonsMethod;
import net.finmath.time.Schedule;
import net.finmath.time.ScheduleGenerator;
import net.finmath.time.ScheduleGenerator.DaycountConvention;
import net.finmath.time.ScheduleGenerator.Frequency;
import net.finmath.time.ScheduleGenerator.ShortPeriodConvention;
import net.finmath.time.businessdaycalendar.BusinessdayCalendar.DateRollConvention;
import net.finmath.time.businessdaycalendar.BusinessdayCalendarExcludingTARGETHolidays;
import net.finmath.time.businessdaycalendar.BusinessdayCalendarExcludingWeekends;

/**
 * This class implements a correlation provider based on iCap market data.
 * The weekly iCap delivery consists of 3M and 6M caplet volatilities.
 * Using those and the formulas from the Schlenkrich paper we can calculate the correlation
 * between forward rates of the form F(0,0.5*i,0.5*i+0.3) and F(0,0.5*i+0.25,0.5*(i+1)).
 * Those aren't enough though to convert tenors from anything else than 3M to 6M.
 *
 * @TODO Calibrate a parameterization as suggested by Schlenkrich given the calculated 3M correlations and use this parameterization to extract all missing correlations.
 *
 * @author Daniel Willhalm
 */
public class CorrelationProviderTenorBasis implements CorrelationProvider {

	private final CapVolMarketData iCap3MCapVolMarketData;
	private final CapVolMarketData iCap6MCapVolMarketData;

	private CapletVolBootstrapping iCap3MCapletVolBootrapper;
	private CapletVolBootstrapping iCap6MCapletVolBootrapper;

	private double[][] iCap3MCapletVolMatrix;
	private double[][] iCap6MCapletVolMatrix;
	private double[][] correlationMatrix1M;
	private double[][] correlationMatrix3M;
	private double[][] correlationMatrix6M;

	public CorrelationProviderTenorBasis(final CapVolMarketData iCap3MCapVolMarketData, final CapVolMarketData iCap6MCapVolMarketData) {
		this.iCap3MCapVolMarketData = iCap3MCapVolMarketData;
		this.iCap6MCapVolMarketData = iCap6MCapVolMarketData;
	}

	public double get6MCorrelation(final double firstForwardFixingTimeVectorInYears, final double secondForwardFixingTimeVectorInYears, final AnalyticModel analyticModel) throws CalculationException {
		//todo: Kalibriere auf Basis der 3M Correlationsdaten die zur Verf�gung stehen eine Parametrisierung der Korrelationen wie sie im Schlenkrich zu finden ist.
		return 0.0;
	}


	public double get3MCorrelation(final double firstForwardFixingTimeVectorInYears, final double secondForwardFixingTimeVectorInYears, final AnalyticModel analyticModel) throws CalculationException {
		//Auf Basis der 3M und 6M Icap daten wird mit der Formel aus Schlenkrich die Korrelation bestimmt. Achtung f�r Umrechung auf 12M br�uchte man mehr 3M Korrelationen die man sich �ber die Parametrisierung holen m�sste.
		final double[] capletFixingTimeVectorInYears = new double[iCap3MCapVolMarketData.getMaxExpiryInMonths()/iCap3MCapVolMarketData.getUnderlyingTenorInMonths()-1];
		for (int i = 0; i < capletFixingTimeVectorInYears.length; i++) {
			capletFixingTimeVectorInYears[i] = (i+1)*iCap3MCapVolMarketData.getUnderlyingTenorInMonths()/12.0;
		}
		int rowIndex = 0;
		int columnIndex = 0;
		double distanceToFixingTimeRow = Double.MAX_VALUE;
		double distanceToFixingTimeColumn = Double.MAX_VALUE;
		for (int i = 0; i < capletFixingTimeVectorInYears.length; i++) {
			if (Math.abs(firstForwardFixingTimeVectorInYears - capletFixingTimeVectorInYears[i]) < distanceToFixingTimeRow) {
				rowIndex = i;
				distanceToFixingTimeRow = Math.abs(firstForwardFixingTimeVectorInYears - capletFixingTimeVectorInYears[i]);
			}
			if (Math.abs(secondForwardFixingTimeVectorInYears - capletFixingTimeVectorInYears[i]) < distanceToFixingTimeColumn) {
				columnIndex = i;
				distanceToFixingTimeColumn = Math.abs(secondForwardFixingTimeVectorInYears - capletFixingTimeVectorInYears[i]);
			}
		}

		//lazy initialization
		if (correlationMatrix3M != null) {
			return correlationMatrix3M[rowIndex][columnIndex];
		}

		iCap3MCapletVolBootrapper = new CapletVolBootstrapping(iCap3MCapVolMarketData, analyticModel);
		iCap6MCapletVolBootrapper = new CapletVolBootstrapping(iCap6MCapVolMarketData, analyticModel);
		if(iCap3MCapletVolMatrix == null || iCap6MCapletVolMatrix == null) {
			iCap3MCapletVolMatrix = iCap3MCapletVolBootrapper.getCapletVolMatrix();
			iCap6MCapletVolMatrix = iCap6MCapletVolBootrapper.getCapletVolMatrix();
		}

		correlationMatrix3M = new double[iCap3MCapVolMarketData.getMaxExpiryInMonths()/iCap3MCapVolMarketData.getUnderlyingTenorInMonths()-1][iCap3MCapVolMarketData.getMaxExpiryInMonths()/iCap3MCapVolMarketData.getUnderlyingTenorInMonths()-1];

		for (int i = 0; i < correlationMatrix3M.length; i++) {
			correlationMatrix3M[i][i] = 1;
		}

		//todo: give interpolation method as an argument
		final SmileInterpolationExtrapolationMethod smileInterExtrapolater3M = new LinearSmileInterpolater(iCap3MCapletVolMatrix, iCap3MCapVolMarketData.getStrikeVector());
		final SmileInterpolationExtrapolationMethod smileInterExtrapolater6M = new LinearSmileInterpolater(iCap6MCapletVolMatrix, iCap6MCapVolMarketData.getStrikeVector());
		final double[][] K = new double[correlationMatrix3M.length/2-(1-correlationMatrix3M.length%2)][2];
		final double[][] nu = new double[correlationMatrix3M.length/2-(1-correlationMatrix3M.length%2)][2];
		final double[] sumNu = new double[correlationMatrix3M.length/2-(1-correlationMatrix3M.length%2)];

		for (int i = 0; i < correlationMatrix3M.length/2-(1-correlationMatrix3M.length%2); i++) {
			final LocalDate localDate = iCap3MCapletVolBootrapper.getDiscountCurve().getReferenceDate();
			final Schedule schedule3M = ScheduleGenerator.createScheduleFromConventions(
					localDate,
					localDate.plusMonths(2*(i+1)*iCap3MCapVolMarketData.getUnderlyingTenorInMonths()),
					localDate.plusMonths(2*(i+2)*iCap3MCapVolMarketData.getUnderlyingTenorInMonths()),
					Frequency.QUARTERLY, DaycountConvention.ACT_365, ShortPeriodConvention.FIRST, DateRollConvention.MODIFIED_FOLLOWING,
					new BusinessdayCalendarExcludingTARGETHolidays(new BusinessdayCalendarExcludingWeekends()), -2, 0);
			final Schedule schedule6M = ScheduleGenerator.createScheduleFromConventions(
					localDate,
					localDate.plusMonths(2*(i+1)*iCap3MCapVolMarketData.getUnderlyingTenorInMonths()),
					localDate.plusMonths(2*(i+2)*iCap3MCapVolMarketData.getUnderlyingTenorInMonths()),
					Frequency.SEMIANNUAL, DaycountConvention.ACT_365, ShortPeriodConvention.FIRST, DateRollConvention.MODIFIED_FOLLOWING,
					new BusinessdayCalendarExcludingTARGETHolidays(new BusinessdayCalendarExcludingWeekends()), -2, 0);
			//tests have shown that the correlations that we get close to the strike are the most meaningful
			//The way the strike is approximated isn't perfect. That's why the first correlation in the test class is > 1
			final double strikeATM = Swap.getForwardSwapRate(schedule3M, schedule3M, iCap3MCapletVolBootrapper.getForwardCurve(), iCap3MCapletVolBootrapper.getParsedModel());
			final double[] shortTenorTau = new double[nu[0].length];
			for (int k = 0; k < shortTenorTau.length; k++) {
				shortTenorTau[k] = schedule3M.getPeriodLength(k);
			}
			final double longTenorTau = schedule6M.getPeriodLength(0);

			for (int k = 0; k < nu[0].length; k++) {
				nu[i][k] = shortTenorTau[k]
						* (1.0 + longTenorTau * iCap6MCapletVolBootrapper.getForwardCurve().getForward(
								iCap6MCapletVolBootrapper.getParsedModel(), capletFixingTimeVectorInYears[i * 2 + 1]))
						/ (longTenorTau * (1.0 + shortTenorTau[k] * iCap3MCapletVolBootrapper.getForwardCurve()
								.getForward(iCap3MCapletVolBootrapper.getParsedModel(),
										capletFixingTimeVectorInYears[i * 2 + 1] + schedule3M.getPeriodStart(k))));
				sumNu[i] += nu[i][k];
			}

			for (int k = 0; k < K[0].length; k++) {
				K[i][k] = (strikeATM - (iCap6MCapletVolBootrapper.getForwardCurve().getForward(
						iCap6MCapletVolBootrapper.getParsedModel(), capletFixingTimeVectorInYears[i * 2 + 1])
						- sumNu[i] * iCap3MCapletVolBootrapper.getForwardCurve()
						.getForward(iCap3MCapletVolBootrapper.getParsedModel(),
								capletFixingTimeVectorInYears[i * 2 + 1]
										+ k * iCap3MCapVolMarketData.getUnderlyingTenorInMonths() / 12.0)))
						/ sumNu[i];
			}

			//newtons method is not really necessary since we search for the root of a linear function but due to convenience we use one iteration of newtons method to find it
			final double guess = 1.0;
			final NewtonsMethod newtonsMethod = new NewtonsMethod(guess);
			double derivativeSumToMinimize = 0.0;
			double secondDerivativeSumToMinimize = 0.0;
			double rightHandSideSum = 0.0;
			double derivativeRightHandSideSum = 0.0;
			for (int k1 = 0; k1 < 2; k1++) {
				for (int k2 = 0; k2 < 2; k2++) {
					if (k1 == k2) {
						rightHandSideSum += nu[i][k1]*nu[i][k2]*smileInterExtrapolater3M.calculateInterpolatedExtrapolatedSmileVolatility(K[i][k1], i*2+1+k1)*smileInterExtrapolater3M.calculateInterpolatedExtrapolatedSmileVolatility(K[i][k2], i*2+1+k2);
					}
					else {
						rightHandSideSum += nu[i][k1]*nu[i][k2]*guess*smileInterExtrapolater3M.calculateInterpolatedExtrapolatedSmileVolatility(K[i][k1], i*2+1+k1)*smileInterExtrapolater3M.calculateInterpolatedExtrapolatedSmileVolatility(K[i][k2], i*2+1+k2);
						derivativeRightHandSideSum += nu[i][k1]*nu[i][k2]*smileInterExtrapolater3M.calculateInterpolatedExtrapolatedSmileVolatility(K[i][k1], i*2+1+k1)*smileInterExtrapolater3M.calculateInterpolatedExtrapolatedSmileVolatility(K[i][k2], i*2+1+k2);
					}
				}
			}
			derivativeSumToMinimize += 2*(smileInterExtrapolater6M.calculateInterpolatedExtrapolatedSmileVolatility(strikeATM, i)*smileInterExtrapolater6M.calculateInterpolatedExtrapolatedSmileVolatility(strikeATM, i)-rightHandSideSum)*(-derivativeRightHandSideSum);
			secondDerivativeSumToMinimize += 2*derivativeRightHandSideSum*derivativeRightHandSideSum;

			newtonsMethod.setValueAndDerivative(derivativeSumToMinimize, secondDerivativeSumToMinimize);
			correlationMatrix3M[i*2+1][i*2+2] = newtonsMethod.getNextPoint();
			correlationMatrix3M[i*2+2][i*2+1] = newtonsMethod.getNextPoint();
		}

		return correlationMatrix3M[rowIndex][columnIndex];
	}

	double get1MCorrelation(final double firstForwardFixingTimeVectorInYears, final double secondForwardFixingTimeVectorInYears, final AnalyticModel analyticModel) {
		return 0;
	}

	public double[][] getiCap3MCapletVolMatrix() {
		return iCap3MCapletVolMatrix;
	}

	public double[][] getiCap6MCapletVolMatrix() {
		return iCap6MCapletVolMatrix;
	}

	public double[][] getCorrelationMatrix3M() {
		return correlationMatrix3M;
	}

	public double[][] getCorrelationMatrix6M() {
		return correlationMatrix6M;
	}

	public CapletVolBootstrapping getICap3MCapletVolBootrapper() {
		return iCap3MCapletVolBootrapper;
	}

	public CapletVolBootstrapping getICap6MCapletVolBootrapper() {
		return iCap6MCapletVolBootrapper;
	}

	@Override
	public double getCorrelation(final int oldTenor, final double firstForwardFixingTimeVectorInYears, final double secondForwardFixingTimeVectorInYears, final AnalyticModel analyticModel, final String indexForDiscount) throws CalculationException {
		if(oldTenor == 6) {
			return get6MCorrelation(firstForwardFixingTimeVectorInYears, secondForwardFixingTimeVectorInYears, analyticModel);
		}
		if(oldTenor == 3) {
			return get3MCorrelation(firstForwardFixingTimeVectorInYears, secondForwardFixingTimeVectorInYears, analyticModel);
		}
		if(oldTenor == 1) {
			return get1MCorrelation(firstForwardFixingTimeVectorInYears, secondForwardFixingTimeVectorInYears, analyticModel);
		}
		throw new IllegalArgumentException("Wrong Tenor for the iCap correlation provider");
	}
}