FDMSabrModel.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 SABR stochastic
 * volatility model.
 *
 * <p>
 * The state variables are {@code (S, alpha)} where
 * </p>
 * <ul>
 *   <li>{@code S} is the spot,</li>
 *   <li>{@code alpha} is the stochastic volatility factor.</li>
 * </ul>
 *
 * <p>
 * Under the risk-neutral dynamics with dividend yield, the model is given by
 * </p>
 * <pre>
 *   dS = (r - q) S dt + alpha S^beta dW_1
 *   d alpha = nu alpha ( rho dW_1 + sqrt(1-rho^2) dW_2 ).
 * </pre>
 *
 * <p>
 * The methods {@link #getDrift(double, double...)} and
 * {@link #getFactorLoading(double, double...)} follow the conventions used in
 * {@link FiniteDifferenceEquityModel}.
 * </p>
 *
 * <p>
 * Although one may formulate the PDE in transformed coordinates such as
 * {@code log(S)}, this class deliberately works in the variables {@code (S,
 * alpha)}
 * so that payoff functions can continue to use the spot directly.
 * </p>
 *
 * <p>
 * Boundary conditions are delegated through {@link FDBoundaryFactory}.
 * </p>
 *
 * @author Alessandro Gnoatto
 */
public class FDMSabrModel implements FiniteDifferenceEquityModel {

	/**
	 * The initial spot.
	 */
	private final double initialSpot;
	/**
	 * The initial alpha.
	 */
	private final double initialAlpha;

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

	// SABR parameters (risk-neutral)
	/**
	 * The beta.
	 */
	private final double beta;
	/**
	 * The nu.
	 */
	private final double nu;
	/**
	 * The rho.
	 */
	private final double rho;

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

	/**
	 * Constructs a SABR finite difference model from discount curves.
	 *
	 * @param initialSpot Initial spot price.
	 * @param initialAlpha Initial volatility factor.
	 * @param riskFreeCurve Risk-free discount curve.
	 * @param dividendYieldCurve Dividend yield discount curve.
	 * @param beta SABR elasticity parameter.
	 * @param nu SABR volatility-of-volatility parameter.
	 * @param rho Correlation between spot and volatility Brownian motions.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMSabrModel(
			final double initialSpot,
			final double initialAlpha,
			final DiscountCurve riskFreeCurve,
			final DiscountCurve dividendYieldCurve,
			final double beta,
			final double nu,
			final double rho,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this.initialSpot = initialSpot;
		this.initialAlpha = initialAlpha;
		this.riskFreeCurve = riskFreeCurve;
		this.dividendYieldCurve = dividendYieldCurve;

		this.beta = beta;
		this.nu = nu;
		this.rho = rho;

		this.spaceTimeDiscretization = spaceTimeDiscretization;
	}

	/**
	 * Constructs a SABR finite difference model with zero dividend yield.
	 *
	 * @param initialSpot Initial spot price.
	 * @param initialAlpha Initial volatility factor.
	 * @param riskFreeCurve Risk-free discount curve.
	 * @param beta SABR elasticity parameter.
	 * @param nu SABR volatility-of-volatility parameter.
	 * @param rho Correlation between spot and volatility Brownian motions.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMSabrModel(
			final double initialSpot,
			final double initialAlpha,
			final DiscountCurve riskFreeCurve,
			final double beta,
			final double nu,
			final double rho,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this.initialSpot = initialSpot;
		this.initialAlpha = initialAlpha;
		this.riskFreeCurve = riskFreeCurve;

		final double[] times = new double[] {0.0, 1.0};
		final double[] givenAnnualizedZeroRates = new double[] {0.0, 0.0};
		final InterpolationMethod interpolationMethod = InterpolationMethod.LINEAR;
		final InterpolationEntity interpolationEntity = InterpolationEntity.LOG_OF_VALUE_PER_TIME;
		final ExtrapolationMethod extrapolationMethod = ExtrapolationMethod.CONSTANT;

		this.dividendYieldCurve =
				DiscountCurveInterpolation.createDiscountCurveFromAnnualizedZeroRates(
						"dividendCurve",
						null,
						times,
						givenAnnualizedZeroRates,
						interpolationMethod,
						extrapolationMethod,
						interpolationEntity);

		this.beta = beta;
		this.nu = nu;
		this.rho = rho;

		this.spaceTimeDiscretization = spaceTimeDiscretization;
	}

	/**
	 * Constructs a SABR finite difference model from constant rates.
	 *
	 * @param initialSpot Initial spot price.
	 * @param initialAlpha Initial volatility factor.
	 * @param riskFreeRate Constant risk-free rate.
	 * @param dividendYieldRate Constant dividend yield rate.
	 * @param beta SABR elasticity parameter.
	 * @param nu SABR volatility-of-volatility parameter.
	 * @param rho Correlation between spot and volatility Brownian motions.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMSabrModel(
			final double initialSpot,
			final double initialAlpha,
			final double riskFreeRate,
			final double dividendYieldRate,
			final double beta,
			final double nu,
			final double rho,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this.initialSpot = initialSpot;
		this.initialAlpha = initialAlpha;

		final double[] times = new double[] {0.0, 1.0};
		final InterpolationMethod interpolationMethod = InterpolationMethod.LINEAR;
		final InterpolationEntity interpolationEntity = InterpolationEntity.VALUE;
		final ExtrapolationMethod extrapolationMethod = ExtrapolationMethod.CONSTANT;

		final double[] givenAnnualizedZeroRates = new double[] {riskFreeRate, riskFreeRate};
		this.riskFreeCurve =
				DiscountCurveInterpolation.createDiscountCurveFromAnnualizedZeroRates(
						"riskFreeCurve",
						null,
						times,
						givenAnnualizedZeroRates,
						interpolationMethod,
						extrapolationMethod,
						interpolationEntity);

		final double[] givenAnnualizedZeroRates1 = new double[] {dividendYieldRate, dividendYieldRate};
		this.dividendYieldCurve =
				DiscountCurveInterpolation.createDiscountCurveFromAnnualizedZeroRates(
						"dividendCurve",
						null,
						times,
						givenAnnualizedZeroRates1,
						interpolationMethod,
						extrapolationMethod,
						interpolationEntity);

		this.beta = beta;
		this.nu = nu;
		this.rho = rho;

		this.spaceTimeDiscretization = spaceTimeDiscretization;
	}

	/**
	 * Constructs a SABR finite difference model from a constant risk-free rate
	 * and zero dividend yield.
	 *
	 * @param initialSpot Initial spot price.
	 * @param initialAlpha Initial volatility factor.
	 * @param riskFreeRate Constant risk-free rate.
	 * @param beta SABR elasticity parameter.
	 * @param nu SABR volatility-of-volatility parameter.
	 * @param rho Correlation between spot and volatility Brownian motions.
	 * @param spaceTimeDiscretization Space-time discretization.
	 */
	public FDMSabrModel(
			final double initialSpot,
			final double initialAlpha,
			final double riskFreeRate,
			final double beta,
			final double nu,
			final double rho,
			final SpaceTimeDiscretization spaceTimeDiscretization) {

		this.initialSpot = initialSpot;
		this.initialAlpha = initialAlpha;

		final double[] times = new double[] {0.0, 1.0};
		final InterpolationMethod interpolationMethod = InterpolationMethod.LINEAR;
		final InterpolationEntity interpolationEntity = InterpolationEntity.VALUE;
		final ExtrapolationMethod extrapolationMethod = ExtrapolationMethod.CONSTANT;

		final double[] givenAnnualizedZeroRates = new double[] {riskFreeRate, riskFreeRate};
		this.riskFreeCurve =
				DiscountCurveInterpolation.createDiscountCurveFromAnnualizedZeroRates(
						"riskFreeCurve",
						null,
						times,
						givenAnnualizedZeroRates,
						interpolationMethod,
						extrapolationMethod,
						interpolationEntity);

		final double[] givenAnnualizedZeroRates1 = new double[] {0.0, 0.0};
		this.dividendYieldCurve =
				DiscountCurveInterpolation.createDiscountCurveFromAnnualizedZeroRates(
						"dividendCurve",
						null,
						times,
						givenAnnualizedZeroRates1,
						interpolationMethod,
						extrapolationMethod,
						interpolationEntity);

		this.beta = beta;
		this.nu = nu;
		this.rho = rho;

		this.spaceTimeDiscretization = spaceTimeDiscretization;
	}

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

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

	/**
	 * Returns the initial value of the state vector {@code (S, alpha)}.
	 *
	 * @return The initial state vector.
	 */
	@Override
	public double[] getInitialValue() {
		return new double[] {initialSpot, initialAlpha};
	}

	/**
	 * Returns the initial spot.
	 *
	 * @return The initial spot.
	 */
	public double getInitialSpot() {
		return initialSpot;
	}

	/**
	 * Returns the initial volatility factor.
	 *
	 * @return The initial volatility factor.
	 */
	public double getInitialAlpha() {
		return initialAlpha;
	}

	/**
	 * Returns the SABR elasticity parameter.
	 *
	 * @return The parameter {@code beta}.
	 */
	public double getBeta() {
		return beta;
	}

	/**
	 * Returns the SABR volatility-of-volatility parameter.
	 *
	 * @return The parameter {@code nu}.
	 */
	public double getNu() {
		return nu;
	}

	/**
	 * Returns the correlation between spot and volatility Brownian motions.
	 *
	 * @return The parameter {@code rho}.
	 */
	public double getRho() {
		return rho;
	}

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

	@Override
	public double[] getDrift(final double time, final double... stateVariables) {
		// stateVariables[0] = S, stateVariables[1] = alpha
		final double s = stateVariables.length > 0 ? stateVariables[0] : initialSpot;
		final double alpha = stateVariables.length > 1 ? Math.max(0.0, stateVariables[1]) : initialAlpha;

		double t = time;
		if (t == 0.0) {
			t = 0.000001;
		}

		final double riskFreeDiscountFactor = getRiskFreeCurve().getDiscountFactor(t);
		final double r = -Math.log(riskFreeDiscountFactor) / t;

		final double dividendDiscountFactor = getDividendYieldCurve().getDiscountFactor(t);
		final double q = -Math.log(dividendDiscountFactor) / t;

		final double muS = (r - q) * s;
		final double muAlpha = 0.0;

		return new double[] {muS, muAlpha};
	}

	@Override
	public double[][] getFactorLoading(final double time, final double... stateVariables) {
		// Factors are independent. Correlation rho is embedded in the second
		// row.
		final double s = stateVariables.length > 0 ? Math.max(0.0, stateVariables[0]) : initialSpot;
		final double alpha = stateVariables.length > 1 ? Math.max(0.0, stateVariables[1]) : initialAlpha;

		final double sToBeta = Math.pow(s, beta);
		final double sqrtOneMinusRho2 = Math.sqrt(Math.max(0.0, 1.0 - rho * rho));

		final double[][] b = new double[2][2];

		// dS = ... + alpha * S^beta dW1
		b[0][0] = alpha * sToBeta;
		b[0][1] = 0.0;

		// d alpha = ... + nu alpha ( rho dW1 + sqrt(1-rho^2) dW2 )
		b[1][0] = nu * alpha * rho;
		b[1][1] = nu * alpha * sqrtOneMinusRho2;

		return b;
	}

	/**
	 * Convenience method returning the covariance matrix {@code a = B B^T},
	 * derived from {@link #getFactorLoading(double, double...)}.
	 *
	 * @param time Evaluation time.
	 * @param stateVariables State variables.
	 * @return The covariance matrix.
	 */
	public double[][] getCovariance(final double time, final double... stateVariables) {
		final double[][] b = getFactorLoading(time, stateVariables);

		final double a00 = b[0][0] * b[0][0] + b[0][1] * b[0][1];
		final double a01 = b[0][0] * b[1][0] + b[0][1] * b[1][1];
		final double a11 = b[1][0] * b[1][0] + b[1][1] * b[1][1];

		return new double[][] {
			{a00, a01},
			{a01, a11}
		};
	}

	@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);
	}

	@Override
	public FiniteDifferenceEquityModel getCloneWithModifiedSpaceTimeDiscretization(
			final SpaceTimeDiscretization newSpaceTimeDiscretization) {
		return new FDMSabrModel(
				initialSpot,
				initialAlpha,
				riskFreeCurve,
				dividendYieldCurve,
				beta,
				nu,
				rho,
				newSpaceTimeDiscretization
				);
	}
}