CancelableSwap.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 net.finmath.exception.CalculationException;
import net.finmath.montecarlo.RandomVariableFromDoubleArray;
import net.finmath.montecarlo.conditionalexpectation.MonteCarloConditionalExpectationRegression;
import net.finmath.montecarlo.interestrate.TermStructureMonteCarloSimulationModel;
import net.finmath.stochastic.ConditionalExpectationEstimator;
import net.finmath.stochastic.RandomVariable;
import net.finmath.stochastic.Scalar;

/**
 * Implements the pricing of a cancelable swap under a <code>LIBORModelMonteCarloSimulationModel</code>
 *
 * @author Christian Fries
 * @version 1.2
 * @date 11.03.2006, 31.05.2009
 */
public class CancelableSwap extends AbstractTermStructureMonteCarloProduct {

	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

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

	/**
	 * 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 {

		// After the last period the product has value zero: Initialize values to zero.
		RandomVariable values = new RandomVariableFromDoubleArray(fixingDates[fixingDates.length-1], 0.0);

		// Loop backward over the swap periods
		for(int period=fixingDates.length-1; period>=0; period--)
		{
			final double fixingDate	= fixingDates[period];
			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);
			final RandomVariable	numeraire               = model.getNumeraire(paymentDate);
			final RandomVariable	monteCarloProbabilities = model.getMonteCarloWeights(paymentDate);

			// 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
			payoff = payoff.div(numeraire).mult(monteCarloProbabilities);

			values = values.add(payoff);

			if(isPeriodStartDateExerciseDate[period]) {
				// Remove foresight through condition expectation
				final ConditionalExpectationEstimator condExpEstimator = getConditionalExpectationEstimator(period, model);

				// Calculate conditional expectation. Note that no discounting (numeraire division) is required!
				final RandomVariable valueIfExcercised         = condExpEstimator.getConditionalExpectation(values);

				// Apply the exercise criteria
				// foreach(path) if(valueIfExcercided.get(path) < 0.0) values[path] = 0.0;
				values = valueIfExcercised.choose(values, new Scalar(0.0));
			}
		}

		final RandomVariable	numeraireAtZero						= model.getNumeraire(evaluationTime);
		final RandomVariable	monteCarloProbabilitiesAtZero		= model.getMonteCarloWeights(evaluationTime);
		values = values.mult(numeraireAtZero).div(monteCarloProbabilitiesAtZero);

		return values;
	}

	/**
	 * @param fixingDateIndex
	 * @param model
	 * @return
	 * @throws CalculationException Thrown if the valuation fails, specific cause may be available via the <code>cause()</code> method.
	 */
	private ConditionalExpectationEstimator getConditionalExpectationEstimator(final int fixingDateIndex, final TermStructureMonteCarloSimulationModel model) throws CalculationException {
		final ConditionalExpectationEstimator condExpEstimator = new MonteCarloConditionalExpectationRegression(getRegressionBasisFunctions(fixingDates[fixingDateIndex], model));
		return condExpEstimator;
	}

	/**
	 * Return the basis functions for the regression suitable for this product.
	 *
	 * @param fixingDate The the fixing date
	 * @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.
	 */
	private RandomVariable[] getRegressionBasisFunctions(final double fixingDate, final TermStructureMonteCarloSimulationModel model) throws CalculationException {

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

		// Constant
		// @TODO Use non differentiable
		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+1]);
		final RandomVariable bondShort = rateShort.mult(paymentDates[fixingDateIndex+1]-fixingDate).add(1.0).invert();
		basisFunctions.add(bondShort);
		basisFunctions.add(bondShort.pow(2.0));
		basisFunctions.add(bondShort.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 bondLong = rateLong.mult(paymentDates[paymentDates.length-1]-fixingDates[fixingDateIndex]).add(1.0).invert();
		basisFunctions.add(bondLong);
		basisFunctions.add(bondLong.pow(2.0));
		basisFunctions.add(bondLong.pow(3.0));

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

		// Cross
		basisFunctions.add(rateLong.mult(numeraire));

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