OptionOnBond.java
package net.finmath.finitedifference.interestrate.products;
import net.finmath.finitedifference.grids.SpaceTimeDiscretization;
import net.finmath.finitedifference.interestrate.models.FiniteDifferenceInterestRateModel;
import net.finmath.finitedifference.solvers.FDMThetaMethod1D;
import net.finmath.modelling.EuropeanExercise;
import net.finmath.modelling.products.CallOrPut;
/**
* Finite-difference valuation of a European option on a deterministic-cashflow
* bond.
*
* <p>
* The underlying bond is assumed to be a {@link Bond}, that is, a bond with
* deterministic fixed cashflows. Let {@code T} denote the option exercise date,
* {@code B(T,x)} the bond value at exercise as a function of the model state,
* {@code K} the strike, and {@code \omega \in \{+1,-1\}} the call/put sign.
* The payoff is
* </p>
*
* <p>
* <i>
* \bigl( \omega ( B(T,x) - K ) \bigr)^{+}.
* </i>
* </p>
*
* <p>
* Since the underlying bond has deterministic cashflows, its value at exercise
* can be computed directly from the interest-rate model through the remaining
* discount bonds:
* </p>
*
* <p>
* <i>
* B(T,x) = \sum_{i:\,T_i \ge T} C_i P(T,T_i;x),
* </i>
* </p>
*
* <p>
* where {@code C_i} are the remaining bond cashflows and {@code P(T,T_i;x)} is
* the discount bond implied by the model.
* </p>
*
* <p>
* The current implementation is intended for one-dimensional finite-difference
* interest-rate models used with {@link FDMThetaMethod1D}.
* </p>
*
* @author Alessandro Gnoatto
*/
public class OptionOnBond implements FiniteDifferenceInterestRateProduct {
/**
* The time tolerance.
*/
private static final double TIME_TOLERANCE = 1E-12;
/**
* The underlying bond.
*/
private final Bond underlyingBond;
/**
* The exercise date.
*/
private final double exerciseDate;
/**
* The strike.
*/
private final double strike;
/**
* The call or put.
*/
private final CallOrPut callOrPut;
/**
* Creates a European option on a deterministic-cashflow bond.
*
* @param underlyingBond The underlying bond.
* @param exerciseDate The option exercise date.
* @param strike The strike.
* @param callOrPut The call/put indicator.
*/
public OptionOnBond(
final Bond underlyingBond,
final double exerciseDate,
final double strike,
final CallOrPut callOrPut) {
if (underlyingBond == null) {
throw new IllegalArgumentException("underlyingBond must not be null.");
}
if (exerciseDate < 0.0) {
throw new IllegalArgumentException("exerciseDate must be non-negative.");
}
if (strike < 0.0) {
throw new IllegalArgumentException("strike must be non-negative.");
}
if (callOrPut == null) {
throw new IllegalArgumentException("callOrPut must not be null.");
}
if (exerciseDate - underlyingBond.getMaturity() > TIME_TOLERANCE) {
throw new IllegalArgumentException(
"exerciseDate must not be later than the maturity of the underlying bond.");
}
this.underlyingBond = underlyingBond;
this.exerciseDate = exerciseDate;
this.strike = strike;
this.callOrPut = callOrPut;
}
@Override
public double[] getValue(
final double evaluationTime,
final FiniteDifferenceInterestRateModel model) {
validateModel(model);
final FDMThetaMethod1D solver = new FDMThetaMethod1D(
model,
this,
model.getSpaceTimeDiscretization(),
new EuropeanExercise(exerciseDate)
);
return solver.getValue(
evaluationTime,
exerciseDate,
buildTerminalValues(model)
);
}
@Override
public double[][] getValues(final FiniteDifferenceInterestRateModel model) {
validateModel(model);
final FDMThetaMethod1D solver = new FDMThetaMethod1D(
model,
this,
model.getSpaceTimeDiscretization(),
new EuropeanExercise(exerciseDate)
);
return solver.getValues(
exerciseDate,
buildTerminalValues(model)
);
}
/**
* Returns the underlying bond.
*
* @return The underlying bond.
*/
public Bond getUnderlyingBond() {
return underlyingBond;
}
/**
* Returns the exercise date.
*
* @return The exercise date.
*/
public double getExerciseDate() {
return exerciseDate;
}
/**
* Returns the strike.
*
* @return The strike.
*/
public double getStrike() {
return strike;
}
/**
* Returns the call/put indicator.
*
* @return The call/put indicator.
*/
public CallOrPut getCallOrPut() {
return callOrPut;
}
private void validateModel(final FiniteDifferenceInterestRateModel model) {
if (model == null) {
throw new IllegalArgumentException("model must not be null.");
}
if (model.getSpaceTimeDiscretization().getNumberOfSpaceGrids() != 1) {
throw new IllegalArgumentException(
"OptionOnBond currently supports only one-dimensional finite-difference interest-rate models."
);
}
}
private double[] buildTerminalValues(final FiniteDifferenceInterestRateModel model) {
final SpaceTimeDiscretization discretization = model.getSpaceTimeDiscretization();
final double[] xGrid = discretization.getSpaceGrid(0).getGrid();
final double[] terminalValues = new double[xGrid.length];
for (int i = 0; i < xGrid.length; i++) {
final double bondValueAtExercise = getUnderlyingBondValueAtExercise(model, xGrid[i]);
terminalValues[i] = Math.max(
callOrPut.toInteger() * (bondValueAtExercise - strike),
0.0
);
}
return terminalValues;
}
private double getUnderlyingBondValueAtExercise(
final FiniteDifferenceInterestRateModel model,
final double stateVariable) {
double value = 0.0;
for (int periodIndex = 0; periodIndex < underlyingBond.getSchedule().getNumberOfPeriods(); periodIndex++) {
final double paymentTime = underlyingBond.getSchedule().getPayment(periodIndex);
if (paymentTime < exerciseDate - TIME_TOLERANCE) {
continue;
}
final double cashflow = underlyingBond.getCashflow(periodIndex);
value += cashflow * model.getDiscountBond(
exerciseDate,
paymentTime,
stateVariable
);
}
return value;
}
}