All files / src/pricing-models BinomialTree.js

100% Statements 29/29
100% Branches 6/6
100% Functions 4/4
100% Lines 25/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87        4x 4x 4x 4x 4x 4x       28x                 28x   28x 28x 15x           28x           28x                       6x 2x         4x     4x 4x 13x 28x         4x 13x 28x                 28x       4x        
import { calculateExerciseValue } from "../utils/OptionUtils.js";
import { isNumberGreaterThanZero } from "../utils/ValidationUtils.js";
 
function determineModelParameters(option, timeSteps) {
  const deltat = option.timeToMaturity / timeSteps;
  const u = Math.exp(option.volatility * Math.sqrt(deltat)); // proportional up movement
  const d = Math.exp(-option.volatility * Math.sqrt(deltat)); // proportional down movement
  const a = Math.exp((option.riskFreeRate - option.dividendYield) * deltat); // growth factor
  const p = (a - d) / (u - d); // probability of an up movement (probability of a down movement is 1 - p)
  return [deltat, u, d, p];
}
 
function createNode(initialSpotPrice, i, j, u, d) {
  return {
    i,
    j,
    spotPrice: initialSpotPrice * u ** j * d ** (i - j),
    value: 0,
  };
}
 
function calculateNodeOptionValue(option, timeSteps, nodes, i, j, deltat, p) {
  const nodeIndex = (i * (i + 1)) / 2 + j; // sum of numbers from 1 to i, plus j
 
  let currentValue = 0;
  if (i < timeSteps) {
    currentValue =
      (p * nodes[nodeIndex + (i + 1) + 1].value +
        (1 - p) * nodes[nodeIndex + (i + 1)].value) *
      Math.exp(-option.riskFreeRate * deltat);
  }
 
  const exerciseValue = calculateExerciseValue(
    option,
    nodes[nodeIndex].spotPrice,
    i === timeSteps ? option.timeToMaturity : i * deltat
  );
 
  return [nodeIndex, Math.max(currentValue, exerciseValue)];
}
 
/**
 * Calculates the price of an option using the binomial options pricing model described by Cox, Ross, and Rubinstein (1979) (https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.379.7582).
 * @param {Option} option option to price
 * @param {object} params
 * @param {number} params.timeSteps number of time steps in the tree (> 0)
 * @returns {number} price of the option
 * @throws if params.timeSteps is not a number greater than zero
 */
function price(option, { timeSteps }) {
  if (!isNumberGreaterThanZero(timeSteps)) {
    throw new Error(
      `invalid time steps (${timeSteps}), must be a number greater than zero.`
    );
  }
 
  const [deltat, u, d, p] = determineModelParameters(option, timeSteps);
 
  // Create the tree
  const nodes = [];
  for (let i = 0; i <= timeSteps; i += 1) {
    for (let j = 0; j <= i; j += 1) {
      nodes.push(createNode(option.initialSpotPrice, i, j, u, d));
    }
  }
 
  // Work backwards through the tree calculating the option values
  for (let i = timeSteps; i >= 0; i -= 1) {
    for (let j = 0; j <= i; j += 1) {
      const [index, value] = calculateNodeOptionValue(
        option,
        timeSteps,
        nodes,
        i,
        j,
        deltat,
        p
      );
      nodes[index].value = value;
    }
  }
 
  return nodes[0].value;
}
 
export { price };