SwaptionHullWhiteModelBoundary.java
package net.finmath.finitedifference.interestrate.boundaries;
import net.finmath.finitedifference.boundaries.BoundaryCondition;
import net.finmath.finitedifference.boundaries.StandardBoundaryCondition;
import net.finmath.finitedifference.interestrate.models.FDMHullWhiteModel;
import net.finmath.finitedifference.interestrate.products.FiniteDifferenceInterestRateProduct;
import net.finmath.finitedifference.interestrate.products.Swaption;
import net.finmath.finitedifference.interestrate.products.Swaption.ResolvedExerciseData;
/**
* Boundary conditions for {@link Swaption} under {@link FDMHullWhiteModel}.
*
* <p>
* This implementation provides asymptotic Dirichlet boundary values compatible
* with European, Bermudan, and American swaption exercise.
* </p>
*
* <p>
* The guiding asymptotics are:
* </p>
* <ul>
* <li>on the deep out-of-the-money side, the swaption value tends to zero,</li>
* <li>on the deep in-the-money side, the swaption value approaches the
* intrinsic value envelope over the remaining admissible exercise
* opportunities.</li>
* </ul>
*
* <p>
* Concretely:
* </p>
* <ul>
* <li>for a {@code RECEIVER} swaption, the lower boundary is treated as the
* deep in-the-money side and the upper boundary as the deep
* out-of-the-money side,</li>
* <li>for a {@code PAYER} swaption, the upper boundary is treated as the deep
* in-the-money side and the lower boundary as the deep out-of-the-money
* side.</li>
* </ul>
*
* <p>
* On the deep in-the-money side, the boundary value is chosen as
* </p>
*
* <p>
* <i>
* \max_{j \in \mathcal{E}(t)} \bigl( V_{\mathrm{swap},j}(t,x), 0 \bigr),
* </i>
* </p>
*
* <p>
* where {@code \mathcal{E}(t)} denotes the set of remaining admissible exercise
* opportunities and {@code V_swap,j(t,x)} is the value of the corresponding
* underlying swap.
* </p>
*
* <p>
* This is a pragmatic boundary choice for the unified PDE swaption class.
* A European-only Jamshidian-exact boundary can be added later as a refinement.
* </p>
*
* @author Alessandro Gnoatto
*/
public class SwaptionHullWhiteModelBoundary implements FiniteDifferenceInterestRateBoundary {
/**
* The time tolerance.
*/
private static final double TIME_TOLERANCE = 1E-12;
/**
* The model.
*/
private final FDMHullWhiteModel model;
/**
* Creates the Hull-White boundary for {@link Swaption}.
*
* @param model The Hull-White model.
*/
public SwaptionHullWhiteModelBoundary(final FDMHullWhiteModel model) {
if (model == null) {
throw new IllegalArgumentException("model must not be null.");
}
if (model.getInitialValue() == null || model.getInitialValue().length != 1) {
throw new IllegalArgumentException(
"SwaptionHullWhiteModelBoundary requires a one-dimensional Hull-White model."
);
}
this.model = model;
}
@Override
public BoundaryCondition[] getBoundaryConditionsAtLowerBoundary(
final FiniteDifferenceInterestRateProduct product,
final double time,
final double... stateVariables) {
final Swaption swaption = validateAndCastProduct(product);
validateStateVariables(stateVariables);
final double boundaryValue = getLowerBoundaryValue(swaption, time, stateVariables[0]);
return new BoundaryCondition[] {
StandardBoundaryCondition.dirichlet(boundaryValue)
};
}
@Override
public BoundaryCondition[] getBoundaryConditionsAtUpperBoundary(
final FiniteDifferenceInterestRateProduct product,
final double time,
final double... stateVariables) {
final Swaption swaption = validateAndCastProduct(product);
validateStateVariables(stateVariables);
final double boundaryValue = getUpperBoundaryValue(swaption, time, stateVariables[0]);
return new BoundaryCondition[] {
StandardBoundaryCondition.dirichlet(boundaryValue)
};
}
private Swaption validateAndCastProduct(final FiniteDifferenceInterestRateProduct product) {
if (!(product instanceof Swaption)) {
throw new IllegalArgumentException(
"SwaptionHullWhiteModelBoundary requires a Swaption product."
);
}
return (Swaption) product;
}
private void validateStateVariables(final double[] stateVariables) {
if (stateVariables == null || stateVariables.length != 1) {
throw new IllegalArgumentException("Exactly one state variable is required.");
}
}
private double getLowerBoundaryValue(
final Swaption swaption,
final double time,
final double stateVariable) {
switch (swaption.getSwaptionType()) {
case RECEIVER:
return getDeepInTheMoneyBoundaryValue(swaption, time, stateVariable);
case PAYER:
return 0.0;
default:
throw new IllegalArgumentException("Unsupported swaption type: " + swaption.getSwaptionType());
}
}
private double getUpperBoundaryValue(
final Swaption swaption,
final double time,
final double stateVariable) {
switch (swaption.getSwaptionType()) {
case RECEIVER:
return 0.0;
case PAYER:
return getDeepInTheMoneyBoundaryValue(swaption, time, stateVariable);
default:
throw new IllegalArgumentException("Unsupported swaption type: " + swaption.getSwaptionType());
}
}
private double getDeepInTheMoneyBoundaryValue(
final Swaption swaption,
final double time,
final double stateVariable) {
final ResolvedExerciseData resolvedExerciseData = swaption.getResolvedExerciseData(model);
double value = 0.0;
for (int i = 0; i < resolvedExerciseData.getNumberOfExerciseTimes(); i++) {
if (resolvedExerciseData.getExerciseTime(i) < time - TIME_TOLERANCE) {
continue;
}
final int scheduleIndex = resolvedExerciseData.getScheduleIndex(i);
value = Math.max(
value,
swaption.getIntrinsicValue(
time,
scheduleIndex,
stateVariable,
model
)
);
}
return value;
}
}