JarrowRuddModel.java

package net.finmath.tree.assetderivativevaluation.models;

import net.finmath.montecarlo.RandomVariableFromDoubleArray;
import net.finmath.stochastic.RandomVariable;
import net.finmath.tree.assetderivativevaluation.AbstractRecombiningTreeModel;

/**
 * This class represents the approximation of a Black-Scholes model via the Jarrow-Rudd model.
 * It extends ApproximatingBinomialModel. The only method that is implemented here computes the values
 * of the up and down movements of the Binomial model.
 *
 * @author Andrea Mazzon
 *
 */

public class JarrowRuddModel extends AbstractRecombiningTreeModel {

	/**
	 * Constructs a Jarrow-Rudd model using an equidistant time step.
	 *
	 * @param spotPrice the initial price of the asset modeled by the process
	 * @param riskFreeRate the number r such that the value of a risk-free bond at time T is e^(rT)
	 * @param volatility the log-volatility of the Black-Scholes model
	 * @param lastTime the last time T in the time discretization {@code 0=t_0<...<t_n=T}
	 * @param timeStep the length t_k - t_{k-1} of the time step
	 */
	public JarrowRuddModel(double spotPrice, double riskFreeRate, double volatility, double lastTime, double timeStep) {
		super(spotPrice, riskFreeRate, volatility, lastTime, timeStep);
	}

	/**
	 * Constructs a Jarrow-Rudd model using a given number of time steps.
	 *
	 * @param spotPrice the initial price of the asset modeled by the process
	 * @param riskFreeRate the number r such that the value of a risk-free bond at time T is e^(rT)
	 * @param volatility the log-volatility of the Black-Scholes model
	 * @param lastTime the last time T in the time discretization {@code 0=t_0<...<t_n=T}
	 * @param numberOfTimes the number of equally spaced time steps
	 */
	public JarrowRuddModel(double spotPrice, double riskFreeRate, double volatility, double lastTime, int numberOfTimes) {
		super(spotPrice, riskFreeRate, volatility, lastTime, numberOfTimes);
	}

	/** Abstract hook to implement in the underlying classes
	 * Binomial: at k level there are k+1 states
	 * @param  k level of depth
	 * */
	@Override
	public int statesAt(int k) {
		return k + 1;
	}
	/** Build S_k[i] with agreement i = #down => S0 * u^(k-i) * d^i.
	 * @param  k level of depth */
	@Override
	protected RandomVariable buildSpotLevel(int k) {
		final double s0    = getInitialPrice();
		final double dt    = getTimeStep();
		final double sigma = getVolatility();
		final double r     = getRiskFreeRate();

		final double muStar = (r - 0.5*sigma*sigma) * dt;
		final double nu     = sigma * Math.sqrt(dt);

		final double u = Math.exp(muStar + nu);
		final double d = Math.exp(muStar - nu);

		final double[] level = new double[k + 1];
		for (int i = 0; i <= k; i++) {
			final int ups   = k - i;
			final int downs = i;
			level[i] = s0 * Math.pow(u, ups) * Math.pow(d, downs);
		}
		return new RandomVariableFromDoubleArray(k * dt, level);
	}

	/**
	 * Discounted conditional expectation : V_k[i] = df() * ( q * V_{k+1}[i] + (1-q) * V_{k+1}[i+1] ).
	 */
	@Override
	protected RandomVariable conditionalExpectation(RandomVariable vNext, int k) {
		final double r = getRiskFreeRate();
		final double dt = getTimeStep();

		final double disc = Math.exp(-r * dt);
		final double p = 0.5;

		final double[] next = vNext.getRealizations();
		final int expected = k + 2;
		if (next == null || next.length != expected) {
			throw new IllegalArgumentException("vNext length " + (next == null ? "null" : next.length)
					+ " != expected " + expected + " for time index k=" + k);
		}

		final int nHere = statesAt(k);
		final double[] res = new double[nHere];
		for (int i = 0; i < nHere; i++) {
			res[i] = disc * (p * next[i] + (1.0 - p) * next[i + 1]);
		}

		return new RandomVariableFromDoubleArray(k * dt, res);
	}

	@Override
	public int getNumberOfBranches(int timeIndex, int stateIndex) {
		return 2;
	}

	@Override
	public double getTransitionProbability(int timeIndex, int stateIndex, int branchIndex) {
		// Convention: 0 = up, 1 = down (Jarrow-Rudd uses p=0.5)
		switch(branchIndex) {
		case 0: return 0.5;
		case 1: return 0.5;
		default: throw new IllegalArgumentException("Invalid branchIndex " + branchIndex + " for binomial model.");
		}
	}

	@Override
	public int[] getChildStateIndexShift() {
		// Convention: childIndex = parentIndex + shift[branchIndex]
		// For binomial recombining trees: up keeps index, down increases index by 1.
		return new int[] { 0, 1 };
	}

}