BarrierOptionHestonModelBoundary.java

package net.finmath.finitedifference.assetderivativevaluation.boundaries;

import net.finmath.finitedifference.assetderivativevaluation.models.FDMHestonModel;
import net.finmath.finitedifference.assetderivativevaluation.products.BarrierOption;
import net.finmath.finitedifference.assetderivativevaluation.products.FiniteDifferenceEquityProduct;
import net.finmath.finitedifference.boundaries.BoundaryCondition;
import net.finmath.finitedifference.boundaries.StandardBoundaryCondition;
import net.finmath.modelling.products.BarrierType;
import net.finmath.modelling.products.CallOrPut;

/**
 * Boundary conditions for {@link BarrierOption} under the {@link
 * FDMHestonModel}.
 *
 * <p>
 * State variables are assumed to be (S, v), where S is the asset level and v
 * the variance.
 * For knock-out options, Dirichlet conditions are imposed on the barrier side
 * in the asset direction.
 * Variance-direction boundaries are left untouched via {@link
 * StandardBoundaryCondition#none()}.
 * </p>
 *
 * <p>
 * The barrier is assumed to coincide with the lower or upper boundary of the
 * asset grid.
 * In-options may still be handled elsewhere by the product logic.
 * </p>
 *
 * <p>
 * This version uses grid-aware continuation-side spot boundary values, with two
 * targeted
 * exceptions motivated by the remaining Heston outlier cases:
 * </p>
 * <ul>
 * <li>for UP_OUT CALL at the lower spot boundary, use {@code none()} instead of
 * a hard zero,</li>
 * <li>for DOWN_OUT PUT at the upper spot boundary, use {@code none()} instead
 * of a hard zero.</li>
 * </ul>
 *
 * @author Alessandro Gnoatto
 */
public class BarrierOptionHestonModelBoundary implements FiniteDifferenceBoundary {

	/**
	 * The epsilon.
	 */
	private static final double EPSILON = 1E-6;

	/**
	 * The model.
	 */
	private final FDMHestonModel model;

	/**
	 * Performs the operation.
	 *
	 * @param model The value.
	 */
	public BarrierOptionHestonModelBoundary(final FDMHestonModel model) {
		this.model = model;
	}

	@Override
	public BoundaryCondition[] getBoundaryConditionsAtLowerBoundary(
			final FiniteDifferenceEquityProduct product,
			double time,
			final double... stateVariables) {

		final BarrierOption option = (BarrierOption) product;
		final BarrierType barrierType = option.getBarrierType();
		final CallOrPut sign = option.getCallOrPut();

		time = Math.max(time, EPSILON);

		final double stock = stateVariables.length > 0 ? stateVariables[0] : 0.0;

		final BoundaryCondition[] result = new BoundaryCondition[2];

		/*
		 * S -> lower boundary
		 *
		 * If the lower boundary is the knock-out barrier, use rebate.
		 *
		 * Targeted relaxation:
		 * For UP_OUT CALL, the lower continuation-side spot boundary was
		 * previously
		 * clamped to zero. We now leave that PDE row free via none().
		 */
		if (barrierType == BarrierType.DOWN_OUT) {
			result[0] = StandardBoundaryCondition.dirichlet(option.getRebate());
		} else if (barrierType == BarrierType.UP_OUT && sign == CallOrPut.CALL) {
			result[0] = StandardBoundaryCondition.none();
		} else {
			result[0] = StandardBoundaryCondition.dirichlet(
					getDiscountedIntrinsicValue(option, time, stock)
					);
		}

		// v -> lower boundary
		result[1] = StandardBoundaryCondition.none();

		return result;
	}

	@Override
	public BoundaryCondition[] getBoundaryConditionsAtUpperBoundary(
			final FiniteDifferenceEquityProduct product,
			double time,
			final double... stateVariables) {

		final BarrierOption option = (BarrierOption) product;
		final BarrierType barrierType = option.getBarrierType();
		final CallOrPut sign = option.getCallOrPut();

		time = Math.max(time, EPSILON);

		final double stock = stateVariables.length > 0 ? stateVariables[0] : 0.0;

		final BoundaryCondition[] result = new BoundaryCondition[2];

		/*
		 * S -> upper boundary
		 *
		 * If the upper boundary is the knock-out barrier, use rebate.
		 *
		 * Targeted relaxation:
		 * For DOWN_OUT PUT, the upper continuation-side spot boundary was
		 * previously
		 * clamped to zero. We now leave that PDE row free via none().
		 */
		if (barrierType == BarrierType.UP_OUT) {
			result[0] = StandardBoundaryCondition.dirichlet(option.getRebate());
		} else if (barrierType == BarrierType.DOWN_OUT && sign == CallOrPut.PUT) {
			result[0] = StandardBoundaryCondition.none();
		} else {
			result[0] = StandardBoundaryCondition.dirichlet(
					getDiscountedIntrinsicValue(option, time, stock)
					);
		}

		// v -> upper boundary
		result[1] = StandardBoundaryCondition.none();

		return result;
	}

	private double getDiscountedIntrinsicValue(
			final BarrierOption option,
			final double evaluationTime,
			final double stock) {

		final double maturity = option.getMaturity();
		final double tau = Math.max(maturity - evaluationTime, 0.0);

		final double discountFactorRiskFree = model.getRiskFreeCurve().getDiscountFactor(tau);
		final double discountFactorDividend = model.getDividendYieldCurve().getDiscountFactor(tau);

		final double discountedForwardLikeSpot = stock * discountFactorDividend;
		final double discountedStrike = option.getStrike() * discountFactorRiskFree;

		if (option.getCallOrPut() == CallOrPut.CALL) {
			return Math.max(discountedForwardLikeSpot - discountedStrike, 0.0);
		} else {
			return Math.max(discountedStrike - discountedForwardLikeSpot, 0.0);
		}
	}
}