BermudanOption.java
package net.finmath.finitedifference.assetderivativevaluation.products;
import net.finmath.finitedifference.FiniteDifferenceExerciseUtil;
import net.finmath.finitedifference.assetderivativevaluation.models.FiniteDifferenceEquityModel;
import net.finmath.finitedifference.grids.Grid;
import net.finmath.finitedifference.grids.SpaceTimeDiscretization;
import net.finmath.finitedifference.solvers.FDMSolver;
import net.finmath.finitedifference.solvers.FDMSolverFactory;
import net.finmath.modelling.BermudanExercise;
import net.finmath.modelling.Exercise;
import net.finmath.modelling.products.CallOrPut;
import net.finmath.time.TimeDiscretization;
/**
* Finite difference valuation of a Bermudan option on a single asset.
*
* <p>
* Exercise times are specified in running time and converted internally to the
* solver's time-to-maturity coordinates through
* {@link FiniteDifferenceExerciseUtil}.
* </p>
*
* @author Alessandro Gnoatto
*/
public class BermudanOption implements FiniteDifferenceEquityProduct {
/**
* The underlying name.
*/
private final String underlyingName;
/**
* The maturity.
*/
private final double maturity;
/**
* The strike.
*/
private final double strike;
/**
* The call or put sign.
*/
private final CallOrPut callOrPutSign;
/**
* The exercise.
*/
private final Exercise exercise;
/**
* Performs the operation.
*
* @param underlyingName The value.
* @param exerciseTimes The value.
* @param strike The value.
* @param callOrPutSign The value.
*/
public BermudanOption(
final String underlyingName,
final double[] exerciseTimes,
final double strike,
final double callOrPutSign) {
this(
underlyingName,
exerciseTimes,
strike,
mapCallOrPut(callOrPutSign)
);
}
/**
* Performs the operation.
*
* @param underlyingName The value.
* @param exerciseTimes The value.
* @param strike The value.
* @param callOrPutSign The value.
*/
public BermudanOption(
final String underlyingName,
final double[] exerciseTimes,
final double strike,
final CallOrPut callOrPutSign) {
if (exerciseTimes == null || exerciseTimes.length == 0) {
throw new IllegalArgumentException("Exercise times must not be null or empty.");
}
this.underlyingName = underlyingName;
this.exercise = new BermudanExercise(exerciseTimes);
this.maturity = this.exercise.getMaturity();
this.strike = strike;
this.callOrPutSign = callOrPutSign;
}
/**
* Performs the operation.
*
* @param exerciseTimes The value.
* @param strike The value.
* @param callOrPutSign The value.
*/
public BermudanOption(
final double[] exerciseTimes,
final double strike,
final double callOrPutSign) {
this(null, exerciseTimes, strike, callOrPutSign);
}
/**
* Performs the operation.
*
* @param exerciseTimes The value.
* @param strike The value.
* @param callOrPutSign The value.
*/
public BermudanOption(
final double[] exerciseTimes,
final double strike,
final CallOrPut callOrPutSign) {
this(null, exerciseTimes, strike, callOrPutSign);
}
@Override
public double[] getValue(final double evaluationTime, final FiniteDifferenceEquityModel model) {
final SpaceTimeDiscretization refinedDiscretization = getRefinedSpaceTimeDiscretization(model);
final FDMSolver solver = FDMSolverFactory.createSolver(model, this, refinedDiscretization, exercise);
if (callOrPutSign == CallOrPut.CALL) {
return solver.getValue(
evaluationTime,
maturity,
assetValue -> Math.max(assetValue - strike, 0.0)
);
} else {
return solver.getValue(
evaluationTime,
maturity,
assetValue -> Math.max(strike - assetValue, 0.0)
);
}
}
@Override
public double[][] getValues(final FiniteDifferenceEquityModel model) {
final SpaceTimeDiscretization refinedDiscretization = getRefinedSpaceTimeDiscretization(model);
final FDMSolver solver = FDMSolverFactory.createSolver(model, this, refinedDiscretization, exercise);
if (callOrPutSign == CallOrPut.CALL) {
return solver.getValues(maturity, assetValue -> Math.max(assetValue - strike, 0.0));
} else {
return solver.getValues(maturity, assetValue -> Math.max(strike - assetValue, 0.0));
}
}
private SpaceTimeDiscretization getRefinedSpaceTimeDiscretization(final FiniteDifferenceEquityModel model) {
final SpaceTimeDiscretization base = model.getSpaceTimeDiscretization();
final TimeDiscretization refinedTimeDiscretization =
FiniteDifferenceExerciseUtil.refineTimeDiscretization(
base.getTimeDiscretization(),
exercise
);
if (base.getNumberOfSpaceGrids() == 1) {
return new SpaceTimeDiscretization(
base.getSpaceGrid(0),
refinedTimeDiscretization,
base.getTheta(),
new double[] {base.getCenter(0) }
);
}
final int numberOfSpaceGrids = base.getNumberOfSpaceGrids();
final Grid[] spaceGrids = new Grid[numberOfSpaceGrids];
final double[] center = new double[numberOfSpaceGrids];
for (int i = 0; i < numberOfSpaceGrids; i++) {
spaceGrids[i] = base.getSpaceGrid(i);
center[i] = base.getCenter(i);
}
return new SpaceTimeDiscretization(
spaceGrids,
refinedTimeDiscretization,
base.getTheta(),
center
);
}
private static CallOrPut mapCallOrPut(final double callOrPutSign) {
if (callOrPutSign == 1.0) {
return CallOrPut.CALL;
}
if (callOrPutSign == -1.0) {
return CallOrPut.PUT;
}
throw new IllegalArgumentException("Unknown option type");
}
/**
* Returns the value.
*
* @return The value.
*/
public String getUnderlyingName() {
return underlyingName;
}
/**
* Returns the value.
*
* @return The value.
*/
public double getMaturity() {
return maturity;
}
/**
* Returns the value.
*
* @return The value.
*/
public double getStrike() {
return strike;
}
/**
* Returns the value.
*
* @return The value.
*/
public CallOrPut getCallOrPut() {
return callOrPutSign;
}
/**
* Returns the value.
*
* @return The value.
*/
public Exercise getExercise() {
return exercise;
}
}