Caplet.java
/*
* (c) Copyright Christian P. Fries, Germany. Contact: email@christian-fries.de.
*
* Created on 09.02.2004
*/
package net.finmath.montecarlo.interestrate.products;
import net.finmath.exception.CalculationException;
import net.finmath.functions.AnalyticFormulas;
import net.finmath.montecarlo.interestrate.TermStructureMonteCarloSimulationModel;
import net.finmath.stochastic.RandomVariable;
/**
* Implements the pricing of a Caplet using a given <code>TermStructureMonteCarloSimulationModel</code>.
*
* @author Christian Fries
* @version 1.0
*/
public class Caplet extends AbstractTermStructureMonteCarloProduct {
public enum ValueUnit {
VALUE,
/**
* @deprecated Use INTEGRATEDLOGNORMALVARIANCE
*/
@Deprecated
INTEGRATEDVARIANCE,
/**
* @deprecated Use LOGNORMALVOLATILITY
*/
@Deprecated
VOLATILITY,
INTEGRATEDLOGNORMALVARIANCE,
LOGNORMALVOLATILITY,
INTEGRATEDNORMALVARIANCE,
NORMALVOLATILITY
}
private final double maturity;
private final double periodLength;
private final double strike;
private final double daycountFraction;
private final boolean isFloorlet;
private final ValueUnit valueUnit;
/**
* Create a caplet or a floorlet.
*
* A caplet pays \( max(L-K,0) * daycountFraction \) at maturity+periodLength
* where L is fixed at maturity.
*
* A floorlet pays \( -min(L-K,0) * daycountFraction \) at maturity+periodLength
* where L is fixed at maturity.
*
* @param maturity The fixing date given as double. The payment is at the period end.
* @param periodLength The length of the forward rate period.
* @param strike The strike given as double.
* @param daycountFraction The daycount fraction used in the payout function.
* @param isFloorlet If true, this object will represent a floorlet, otherwise a caplet.
* @param valueUnit The unit of the value returned by the <code>getValue</code> method.
*/
public Caplet(final double maturity, final double periodLength, final double strike, final double daycountFraction, final boolean isFloorlet, final ValueUnit valueUnit) {
super();
this.maturity = maturity;
this.periodLength = periodLength;
this.strike = strike;
this.daycountFraction = daycountFraction;
this.isFloorlet = isFloorlet;
this.valueUnit = valueUnit;
}
/**
* Create a caplet or a floorlet.
*
* A caplet pays \( max(L-K,0) * daycountFraction \) at maturity+periodLength
* where L is fixed at maturity.
*
* A floorlet pays \( -min(L-K,0) * daycountFraction \) at maturity+periodLength
* where L is fixed at maturity.
*
* This simplified constructor uses daycountFraction = periodLength.
*
* @param maturity The fixing date given as double. The payment is at the period end.
* @param periodLength The length of the forward rate period in ACT/365 convention.
* @param strike The strike given as double.
* @param isFloorlet If true, this object will represent a floorlet, otherwise a caplet.
*/
public Caplet(final double maturity, final double periodLength, final double strike, final boolean isFloorlet) {
this(maturity, periodLength, strike, periodLength, isFloorlet, ValueUnit.VALUE);
}
/**
* Create a caplet.
*
* A caplet pays \( max(L-K,0) * periodLength \) at maturity+periodLength
* where L is fixed at maturity.
*
* @param maturity The fixing date given as double. The payment is at the period end.
* @param periodLength The length of the forward rate period.
* @param strike The strike given as double.
*/
public Caplet(final double maturity, final double periodLength, final double strike) {
this(maturity, periodLength, strike, false);
}
/**
* 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 {
// This is on the LIBOR discretization
final double paymentDate = maturity+periodLength;
// Get random variables
final RandomVariable forwardRate = model.getForwardRate(maturity, maturity, maturity+periodLength);
final RandomVariable numeraire = model.getNumeraire(paymentDate);
final RandomVariable monteCarloProbabilities = model.getMonteCarloWeights(paymentDate);
/*
* Calculate the payoff, which is
* max(L-K,0) * periodLength for caplet or
* -min(L-K,0) * periodLength for floorlet.
*/
RandomVariable values = forwardRate;
if(!isFloorlet) {
values = values.sub(strike).floor(0.0).mult(daycountFraction);
} else {
values = values.sub(strike).cap(0.0).mult(-1.0 * daycountFraction);
}
values = values.div(numeraire).mult(monteCarloProbabilities);
final RandomVariable numeraireAtValuationTime = model.getNumeraire(evaluationTime);
final RandomVariable monteCarloProbabilitiesAtValuationTime = model.getMonteCarloWeights(evaluationTime);
values = values.mult(numeraireAtValuationTime).div(monteCarloProbabilitiesAtValuationTime);
if(valueUnit == ValueUnit.VALUE) {
return values;
}
else if(valueUnit == ValueUnit.LOGNORMALVOLATILITY || valueUnit == ValueUnit.VOLATILITY) {
/*
* This calculation makes sense only if the value is an unconditional one.
*/
final double discountFactor = monteCarloProbabilities.div(numeraire).mult(numeraireAtValuationTime).div(monteCarloProbabilitiesAtValuationTime).getAverage();
final double forward = forwardRate.div(numeraire).mult(monteCarloProbabilities).mult(numeraireAtValuationTime).div(monteCarloProbabilitiesAtValuationTime).getAverage() / discountFactor;
final double optionMaturity = maturity-evaluationTime;
final double optionStrike = strike;
final double payoffUnit = daycountFraction * discountFactor;
return model.getRandomVariableForConstant(AnalyticFormulas.blackScholesOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, values.getAverage()));
}
else if(valueUnit == ValueUnit.NORMALVOLATILITY) {
/*
* This calculation makes sense only if the value is an unconditional one.
*/
final double discountFactor = monteCarloProbabilities.div(numeraire).mult(numeraireAtValuationTime).div(monteCarloProbabilitiesAtValuationTime).getAverage();
final double forward = forwardRate.div(numeraire).mult(monteCarloProbabilities).mult(numeraireAtValuationTime).div(monteCarloProbabilitiesAtValuationTime).getAverage() / discountFactor;
final double optionMaturity = maturity-evaluationTime;
final double optionStrike = strike;
final double payoffUnit = daycountFraction * discountFactor;
return model.getRandomVariableForConstant(AnalyticFormulas.bachelierOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, values.getAverage()));
}
else {
throw new IllegalArgumentException("Value unit " + valueUnit + " unsupported.");
}
}
}