FDMBachelierModel.java

package net.finmath.finitedifference.assetderivativevaluation.models;

import net.finmath.finitedifference.assetderivativevaluation.boundaries.FDBoundaryFactory;
import net.finmath.finitedifference.assetderivativevaluation.boundaries.FiniteDifferenceBoundary;
import net.finmath.finitedifference.assetderivativevaluation.products.FiniteDifferenceEquityProduct;
import net.finmath.finitedifference.boundaries.BoundaryCondition;
import net.finmath.finitedifference.grids.SpaceTimeDiscretization;
import net.finmath.marketdata.model.curves.CurveInterpolation.ExtrapolationMethod;
import net.finmath.marketdata.model.curves.CurveInterpolation.InterpolationEntity;
import net.finmath.marketdata.model.curves.CurveInterpolation.InterpolationMethod;
import net.finmath.marketdata.model.curves.DiscountCurve;
import net.finmath.marketdata.model.curves.DiscountCurveInterpolation;

/**
 * Finite difference model for option pricing under the Bachelier (normal)
 * model.
 *
 * <p>
 * The model assumes an arithmetic Brownian motion for the underlying:
 * </p>
 *
 * <pre>
 * dS(t) = (r(t) - q(t)) S(t) dt + sigma dW(t)
 * </pre>
 *
 * <p>
 * where {@code r(t)} is the risk-free rate, {@code q(t)} is the dividend yield,
 * and {@code sigma} is a
 * constant (normal) volatility.
 * </p>
 *
 * <p>
 * This class follows the same design principles as the other finite-difference
 * equity model implementations:
 * it provides drift and factor loadings via {@link #getDrift(double,
 * double...)} and
 * {@link #getFactorLoading(double, double...)}, and delegates boundary values
 * to a boundary implementation
 * created through {@link FDBoundaryFactory}.
 * </p>
 *
 * @author Alessandro Gnoatto
 */
public class FDMBachelierModel implements FiniteDifferenceEquityModel {

	/**
	 * The initial value.
	 */
	private final double initialValue;

	/**
	 * The risk free curve.
	 */
	private final DiscountCurve riskFreeCurve;
	/**
	 * The dividend yield curve.
	 */
	private final DiscountCurve dividendYieldCurve;

	/**
	 * The volatility.
	 */
	private final double volatility;

	/**
	 * The space time discretization.
	 */
	private final SpaceTimeDiscretization spaceTimeDiscretization;

	/**
	 * Creates a Bachelier model from discount curves.
	 *
	 * @param initialValue            Initial underlying value {@code S(0)}.
	 * @param riskFreeCurve           Risk-free discount curve.
	 * @param dividendYieldCurve      Dividend yield discount curve.
	 * @param volatility              Normal volatility {@code sigma}.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMBachelierModel(
			final double initialValue,
			final DiscountCurve riskFreeCurve,
			final DiscountCurve dividendYieldCurve,
			final double volatility,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this.initialValue = initialValue;
		this.riskFreeCurve = riskFreeCurve;
		this.dividendYieldCurve = dividendYieldCurve;
		this.volatility = volatility;
		this.spaceTimeDiscretization = spaceTimeDiscretization;
	}

	/**
	 * Convenience constructor without dividend yield curve (i.e., {@code q =
	 * 0}).
	 *
	 * @param initialValue            Initial underlying value {@code S(0)}.
	 * @param riskFreeCurve           Risk-free discount curve.
	 * @param volatility              Normal volatility {@code sigma}.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMBachelierModel(
			final double initialValue,
			final DiscountCurve riskFreeCurve,
			final double volatility,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this(
				initialValue,
				riskFreeCurve,
				createFlatZeroCurve("dividendCurve"),
				volatility,
				spaceTimeDiscretization);
	}

	/**
	 * Creates a Bachelier model from constant rates (flat curves).
	 *
	 * @param initialValue            Initial underlying value {@code S(0)}.
	 * @param riskFreeRate            Constant risk-free rate {@code r}.
	 * @param dividendYieldRate       Constant dividend yield {@code q}.
	 * @param volatility              Normal volatility {@code sigma}.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMBachelierModel(
			final double initialValue,
			final double riskFreeRate,
			final double dividendYieldRate,
			final double volatility,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this(
				initialValue,
				createFlatCurveFromZeroRate("riskFreeCurve", riskFreeRate),
				createFlatCurveFromZeroRate("dividendCurve", dividendYieldRate),
				volatility,
				spaceTimeDiscretization);
	}

	/**
	 * Convenience constructor from a constant risk-free rate (flat curve) and
	 * {@code q = 0}.
	 *
	 * @param initialValue            Initial underlying value {@code S(0)}.
	 * @param riskFreeRate            Constant risk-free rate {@code r}.
	 * @param volatility              Normal volatility {@code sigma}.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMBachelierModel(
			final double initialValue,
			final double riskFreeRate,
			final double volatility,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this(initialValue, riskFreeRate, 0.0, volatility, spaceTimeDiscretization);
	}

	@Override
	public DiscountCurve getRiskFreeCurve() {
		return riskFreeCurve;
	}

	@Override
	public DiscountCurve getDividendYieldCurve() {
		return dividendYieldCurve;
	}

	/**
	 * Returns {@code S(0)}.
	 *
	 * @return Initial underlying value.
	 */
	@Override
	public double[] getInitialValue() {
		return new double[] {initialValue};
	}

	/**
	 * Returns the normal volatility {@code sigma}.
	 *
	 * @return Normal volatility.
	 */
	public double getVolatility() {
		return volatility;
	}

	@Override
	public SpaceTimeDiscretization getSpaceTimeDiscretization() {
		return spaceTimeDiscretization;
	}

	@Override
	public double[] getDrift(final double time, final double... stateVariables) {
		final double s = stateVariables.length > 0 ? stateVariables[0] : initialValue;

		final double tSafe = time == 0.0 ? 1e-6 : time;

		final double r = -Math.log(riskFreeCurve.getDiscountFactor(tSafe)) / tSafe;
		final double q = -Math.log(dividendYieldCurve.getDiscountFactor(tSafe)) / tSafe;

		// Bachelier drift in spot coordinates: (r-q) * S
		return new double[] {(r - q) * s };
	}

	@Override
	public double[][] getFactorLoading(final double time, final double... stateVariables) {
		// Single factor, additive diffusion: sigma dW
		return new double[][] {{volatility } };
	}

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

		final FiniteDifferenceBoundary boundary =
				FDBoundaryFactory.createBoundary(this, product);

		return boundary.getBoundaryConditionsAtLowerBoundary(product, time, riskFactors);
	}

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

		final FiniteDifferenceBoundary boundary =
				FDBoundaryFactory.createBoundary(this, product);

		return boundary.getBoundaryConditionsAtUpperBoundary(product, time, riskFactors);
	}

	private static DiscountCurve createFlatZeroCurve(final String name) {
		return createFlatCurveFromZeroRate(name, 0.0);
	}

	private static DiscountCurve createFlatCurveFromZeroRate(final String name, final double zeroRate) {
		final double[] times = new double[] {0.0, 1.0 };
		final double[] zeroRates = new double[] {zeroRate, zeroRate };

		final InterpolationMethod interpolationMethod = InterpolationMethod.LINEAR;
		final ExtrapolationMethod extrapolationMethod = ExtrapolationMethod.CONSTANT;
		final InterpolationEntity interpolationEntity = InterpolationEntity.LOG_OF_VALUE_PER_TIME;

		return DiscountCurveInterpolation.createDiscountCurveFromAnnualizedZeroRates(
				name,
				null,
				times,
				zeroRates,
				interpolationMethod,
				extrapolationMethod,
				interpolationEntity);
	}

	@Override
	public FiniteDifferenceEquityModel getCloneWithModifiedSpaceTimeDiscretization(
			final SpaceTimeDiscretization newSpaceTimeDiscretization) {
		return new FDMBachelierModel(
				initialValue,
				riskFreeCurve,
				dividendYieldCurve,
				volatility,
				newSpaceTimeDiscretization
				);
	}
}