BermudanSwaption.java

/*
 * (c) Copyright Christian P. Fries, Germany. Contact: email@christian-fries.de.
 *
 * Created on 11.03.2006
 */
package net.finmath.montecarlo.interestrate.products;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import net.finmath.exception.CalculationException;
import net.finmath.montecarlo.MonteCarloSimulationModel;
import net.finmath.montecarlo.RandomVariableFromDoubleArray;
import net.finmath.montecarlo.conditionalexpectation.MonteCarloConditionalExpectationRegression;
import net.finmath.montecarlo.conditionalexpectation.RegressionBasisFunctionsProvider;
import net.finmath.montecarlo.interestrate.LIBORModelMonteCarloSimulationModel;
import net.finmath.montecarlo.interestrate.TermStructureMonteCarloSimulationModel;
import net.finmath.stochastic.ConditionalExpectationEstimator;
import net.finmath.stochastic.RandomVariable;
import net.finmath.stochastic.Scalar;

/**
 * Implements the valuation of a Bermudan swaption under a <code>LIBORModelMonteCarloSimulationModel</code>
 *
 * @author Christian Fries
 * @version 1.3
 * @date 06.12.2009
 */
public class BermudanSwaption extends AbstractTermStructureMonteCarloProduct implements RegressionBasisFunctionsProvider {

	private final boolean[]	isPeriodStartDateExerciseDate;	// Exercise date
	private final double[]	fixingDates;                  	// Vector of fixing dates (must be sorted)
	private final double[]	periodLengths;					// Vector of period length;
	private final double[]	paymentDates;	                // Vector of payment dates (same length as fixing dates)
	private final double[]	periodNotionals;				// Vector of notionals for each period
	private final double[]	swaprates;	                 	// Vector of strikes
	private final boolean	isCallable;						// If true: the right to enter into a swap, else the right to terminate a swap.

	private final RegressionBasisFunctionsProvider	regressionBasisFunctionsProvider;

	/**
	 * @param isPeriodStartDateExerciseDate If true, we may exercise at period start
	 * @param fixingDates Vector of fixing dates
	 * @param periodLength Period lengths (must have same length as fixing dates)
	 * @param paymentDates Vector of payment dates (must have same length as fixing dates)
	 * @param periodNotionals Period notionals (must have same length as fixing dates)
	 * @param swaprates Vector of strikes (must have same length as fixing dates)
	 * @param isCallable If true, the product represent the Bermudan right to enter into a swap. If false the product represents the Bermudan right to terminate a running swap.
	 * @param regressionBasisFunctionsProvider Used to determine the regression basis functions for the conditional expectation operator.
	 */
	public BermudanSwaption(final boolean[] isPeriodStartDateExerciseDate, final double[] fixingDates, final double[] periodLength, final double[] paymentDates, final double[] periodNotionals, final double[] swaprates, final boolean isCallable, final RegressionBasisFunctionsProvider	regressionBasisFunctionsProvider) {
		super();
		this.isPeriodStartDateExerciseDate = isPeriodStartDateExerciseDate;
		this.fixingDates = fixingDates;
		periodLengths = periodLength;
		this.paymentDates = paymentDates;
		this.periodNotionals = periodNotionals;
		this.swaprates = swaprates;
		this.isCallable = isCallable;
		this.regressionBasisFunctionsProvider = regressionBasisFunctionsProvider;
	}

	/**
	 * @param isPeriodStartDateExerciseDate If true, we may exercise at period start
	 * @param fixingDates Vector of fixing dates
	 * @param periodLength Period lengths (must have same length as fixing dates)
	 * @param paymentDates Vector of payment dates (must have same length as fixing dates)
	 * @param periodNotionals Period notionals (must have same length as fixing dates)
	 * @param swaprates Vector of strikes (must have same length as fixing dates)
	 * @param isCallable If true, the product represent the Bemrudan right to enter into a swap. If false the product represents the Bermudan right to terminate a running swap.
	 */
	public BermudanSwaption(final boolean[] isPeriodStartDateExerciseDate, final double[] fixingDates, final double[] periodLength, final double[] paymentDates, final double[] periodNotionals, final double[] swaprates, final boolean isCallable) {
		this(isPeriodStartDateExerciseDate, fixingDates, periodLength, paymentDates, periodNotionals, swaprates, isCallable, null);
	}

	/**
	 * @param isPeriodStartDateExerciseDate If true, we may exercise at period start
	 * @param fixingDates Vector of fixing dates
	 * @param periodLength Period lengths (must have same length as fixing dates)
	 * @param paymentDates Vector of payment dates (must have same length as fixing dates)
	 * @param periodNotionals Period notionals (must have same length as fixing dates)
	 * @param swaprates Vector of strikes (must have same length as fixing dates)
	 */
	public BermudanSwaption(final boolean[] isPeriodStartDateExerciseDate, final double[] fixingDates, final double[] periodLength, final double[] paymentDates, final double[] periodNotionals, final double[] swaprates) {
		this(isPeriodStartDateExerciseDate, fixingDates, periodLength, paymentDates, periodNotionals, swaprates, true /* isCallable */);
	}

	@Override
	public Map<String, Object> getValues(final double evaluationTime, final TermStructureMonteCarloSimulationModel model) throws CalculationException {

		// After the last period the product has value zero: Initialize values to zero.
		RandomVariable values				= model.getRandomVariableForConstant(0.0);
		RandomVariable valuesUnderlying	= model.getRandomVariableForConstant(0.0);
		RandomVariable	exerciseTime	    = model.getRandomVariableForConstant(Double.POSITIVE_INFINITY);

		// Loop backward over the swap periods
		for(int period=fixingDates.length-1; period>=0; period--)
		{
			final double fixingDate	= fixingDates[period];
			final double exerciseDate = fixingDate;
			final double periodLength	= periodLengths[period];
			final double paymentDate	= paymentDates[period];
			final double notional		= periodNotionals[period];
			final double swaprate		= swaprates[period];

			// Get random variables - note that this is the rate at simulation time = exerciseDate
			final RandomVariable	libor					= model.getForwardRate(fixingDate, fixingDate, fixingDate+periodLength);

			// foreach(path) values[path] += notional * (libor.get(path) - swaprate) * periodLength / numeraire.get(path) * monteCarloProbabilities.get(path);
			RandomVariable payoff = libor.sub(swaprate).mult(periodLength).mult(notional);

			// Apply discounting and Monte-Carlo probabilities
			final RandomVariable	numeraire               = model.getNumeraire(paymentDate);
			final RandomVariable	monteCarloProbabilities = model.getMonteCarloWeights(paymentDate);
			payoff = payoff.div(numeraire).mult(monteCarloProbabilities);

			//			model.discount(paymentDate, values);
			if(isCallable) {
				valuesUnderlying = valuesUnderlying.add(payoff);
			}
			else
			{
				values = values.add(payoff); // cancelable
			}

			if(isPeriodStartDateExerciseDate[period]) {
				final RandomVariable triggerValuesDiscounted = values.sub(valuesUnderlying);

				// Remove foresight through condition expectation
				final ConditionalExpectationEstimator conditionalExpectationOperator = getConditionalExpectationEstimator(fixingDate, model);

				// Calculate conditional expectation. Note that no discounting (numeraire division) is required!
				final RandomVariable triggerValues         = triggerValuesDiscounted.getConditionalExpectation(conditionalExpectationOperator);

				// Apply the exercise criteria
				// foreach(path) if(valueIfExcercided.get(path) < 0.0) values[path] = 0.0;
				values = triggerValues.choose(values, valuesUnderlying);

				exerciseTime = triggerValues.choose(exerciseTime, new Scalar(exerciseDate));
			}
		}

		//		model.discount(evaluationTime, values);

		// Note that values is a relative price - no numeraire division is required
		final RandomVariable	numeraireAtZero					= model.getNumeraire(evaluationTime);
		final RandomVariable	monteCarloProbabilitiesAtZero	= model.getMonteCarloWeights(evaluationTime);
		values = values.mult(numeraireAtZero).div(monteCarloProbabilitiesAtZero);

		final Map<String, Object> results = new HashMap<>();
		results.put("value", values);
		results.put("error", values.getStandardError());
		results.put("exerciseTime", exerciseTime);
		return results;
	}

	/**
	 * This method returns the value random variable of the product within the specified model, evaluated at a given evalutationTime.
	 * Note: For a lattice this is often the value conditional to evalutationTime, for a Monte-Carlo simulation this is the (sum of) value discounted to evaluation time.
	 * Cashflows prior evaluationTime are not considered.
	 *
	 * @param evaluationTime The time on which this products value should be observed.
	 * @param model The model used to price the product.
	 * @return The random variable representing the value of the product discounted to evaluation time
	 * @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via the <code>cause()</code> method.
	 */
	@Override
	public RandomVariable getValue(final double evaluationTime, final TermStructureMonteCarloSimulationModel model) throws CalculationException {
		return (RandomVariable) getValues(evaluationTime, model).get("value");
	}

	/**
	 * Return the conditional expectation estimator suitable for this product.
	 *
	 * @param fixingDate The condition time.
	 * @param model The model
	 * @return The conditional expectation estimator suitable for this product
	 * @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via the <code>cause()</code> method.
	 */
	public ConditionalExpectationEstimator getConditionalExpectationEstimator(final double fixingDate, final TermStructureMonteCarloSimulationModel model) throws CalculationException {
		final RandomVariable[] regressionBasisFunctions			= regressionBasisFunctionsProvider != null ? regressionBasisFunctionsProvider.getBasisFunctions(fixingDate, model) : getBasisFunctions(fixingDate, model);
		final MonteCarloConditionalExpectationRegression condExpEstimator = new MonteCarloConditionalExpectationRegression(regressionBasisFunctions);

		return condExpEstimator;
	}

	/**
	 * Return the basis functions for the regression suitable for this product.
	 *
	 * @param fixingDate The condition time.
	 * @param model The model
	 * @return The basis functions for the regression suitable for this product.
	 * @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via the <code>cause()</code> method.
	 */
	@Override
	public RandomVariable[] getBasisFunctions(final double fixingDate, final MonteCarloSimulationModel model) throws CalculationException {
		if(model instanceof LIBORModelMonteCarloSimulationModel) {
			return getBasisFunctions(fixingDate, (LIBORModelMonteCarloSimulationModel)model);
		}
		else {
			throw new IllegalArgumentException("Requires model to implement LIBORModelMonteCarloSimulationModel.");
		}
	}

	/**
	 * Return the basis functions for the regression suitable for this product.
	 *
	 * @param fixingDate The condition time.
	 * @param model The model
	 * @return The basis functions for the regression suitable for this product.
	 * @throws net.finmath.exception.CalculationException Thrown if the valuation fails, specific cause may be available via the <code>cause()</code> method.
	 */
	public RandomVariable[] getBasisFunctions(final double fixingDate, final LIBORModelMonteCarloSimulationModel model) throws CalculationException {

		final ArrayList<RandomVariable> basisFunctions = new ArrayList<>();

		// Constant
		final RandomVariable basisFunction = new RandomVariableFromDoubleArray(1.0);//.getRandomVariableForConstant(1.0);
		basisFunctions.add(basisFunction);

		int fixingDateIndex = Arrays.binarySearch(fixingDates, fixingDate);
		if(fixingDateIndex < 0) {
			fixingDateIndex = -fixingDateIndex;
		}
		if(fixingDateIndex >= fixingDates.length) {
			fixingDateIndex = fixingDates.length-1;
		}

		// forward rate to the next period
		final RandomVariable rateShort = model.getForwardRate(fixingDate, fixingDate, paymentDates[fixingDateIndex]);
		final RandomVariable discountShort = rateShort.mult(paymentDates[fixingDateIndex]-fixingDate).add(1.0).invert();
		basisFunctions.add(discountShort);
		basisFunctions.add(discountShort.pow(2.0));
		//		basisFunctions.add(rateShort.pow(3.0));

		// forward rate to the end of the product
		final RandomVariable rateLong = model.getForwardRate(fixingDate, fixingDates[fixingDateIndex], paymentDates[paymentDates.length-1]);
		final RandomVariable discountLong = rateLong.mult(paymentDates[paymentDates.length-1]-fixingDates[fixingDateIndex]).add(1.0).invert();
		basisFunctions.add(discountLong);
		basisFunctions.add(discountLong.pow(2.0));
		//		basisFunctions.add(rateLong.pow(3.0));

		// Numeraire
		final RandomVariable numeraire = model.getNumeraire(fixingDate).invert();
		basisFunctions.add(numeraire);
		//		basisFunctions.add(numeraire.pow(2.0));
		//		basisFunctions.add(numeraire.pow(3.0));

		return basisFunctions.toArray(new RandomVariable[basisFunctions.size()]);
	}

	public double[] getExerciseTimes(){
		final ArrayList<Double> exerciseTimes = new ArrayList<>();
		for(int i=0;i<isPeriodStartDateExerciseDate.length;i++) {
			if(isPeriodStartDateExerciseDate[i]){
				exerciseTimes.add(fixingDates[i]);
			}
		}

		// Convert to primitive
		final double[] times = new double[exerciseTimes.size()];
		for(int i=0;i<times.length;i++) {
			times[i]=exerciseTimes.get(i).doubleValue();
		}
		return times;
	}

	public double[] getFixingDates(final double evaluationTime){ // return the remaining fixing dates after evaluationTime
		final ArrayList<Double> remainingFixingTimes = new ArrayList<>();
		for(int i=0;i<fixingDates.length;i++) {
			if(fixingDates[i]>=evaluationTime){
				remainingFixingTimes.add(fixingDates[i]);
			}
		}
		// Convert to primitive
		final double[] times = new double[remainingFixingTimes.size()];
		for(int i=0;i<times.length;i++) {
			times[i]=remainingFixingTimes.get(i).doubleValue();
		}
		return times;
	}

	public SimpleSwap getSwap() {
		return new SimpleSwap(fixingDates, paymentDates, swaprates, true, periodNotionals);
	}

	public double[] getPaymentDates(){
		return paymentDates;
	}

	public double[] getPeriodNotionals(){
		return periodNotionals;
	}

	public double[] getSwapRates(){
		return swaprates;
	}

	public double[] getPeriodLengths(){
		return periodLengths;
	}

	public double getFinalMaturity() {
		return paymentDates[paymentDates.length-1];
	}

	public boolean getIsCallable(){
		return isCallable;
	}
}