/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.option.pricing.tree; import org.apache.commons.lang.Validate; import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve; import com.opengamma.analytics.financial.model.option.definition.OptionDefinition; import com.opengamma.analytics.financial.model.option.definition.OptionExerciseFunction; import com.opengamma.analytics.financial.model.option.definition.OptionPayoffFunction; import com.opengamma.analytics.financial.model.option.definition.StandardOptionDataBundle; import com.opengamma.analytics.financial.model.tree.RecombiningBinomialTree; import com.opengamma.util.tuple.DoublesPair; /** * Builds a binomial tree * @param <T> StandardOptionDataBundle */ public abstract class BinomialTreeBuilder<T extends StandardOptionDataBundle> { /** * Builds a tree of an asset prices * @param maturity The time span (in years) of the tree * @param data OptionDataBundle * @param nSteps The number of steps in the tree (need at least 1 step) * @return tree of an asset prices */ @SuppressWarnings("unchecked") public RecombiningBinomialTree<BinomialTreeNode<Double>> buildAssetTree(final double maturity, final T data, final int nSteps) { final BinomialTreeNode<Double>[][] tree = new BinomialTreeNode[nSteps + 1][]; double t = 0; final double spot = data.getSpot(); final double dt = maturity / nSteps; double[] spots = new double[1]; spots[0] = spot; for (int i = 1; i <= nSteps; i++) { t = (i - 1) * dt; final double[] forwards = getForwards(spots, data, t, dt); final double[] nodes = new double[i + 1]; int jPlus; int jMinus; if (i % 2 == 0) { // central node set equal to spot final int k = i / 2; jPlus = k + 1; jMinus = k - 1; nodes[k] = spot; // TODO have an option for the centre node to follow the forward rather than the spot } else { final int k = (i - 1) / 2; jPlus = k + 2; jMinus = k - 1; final double sigma = data.getVolatility(t, spots[k]); final DoublesPair nodePair = getCentralNodePair(dt, sigma, forwards[k], spot); nodes[k] = nodePair.first; nodes[k + 1] = nodePair.second; } for (int j = jPlus; j <= i; j++) { final double sigma = data.getVolatility(t, spots[j - 1]); nodes[j] = getNextHigherNode(dt, sigma, forwards[j - 1], nodes[j - 1]); } for (int j = jMinus; j >= 0; j--) { final double sigma = data.getVolatility(t, spots[j]); nodes[j] = getNextLowerNode(dt, sigma, forwards[j], nodes[j + 1]); } tree[i - 1] = new BinomialTreeNode[i]; for (int j = 0; j < i; j++) { final double diff = nodes[j + 1] - nodes[j]; double p; if (diff == 0.0) { // some branches of the tree are stuck at spot = 0.0 - this is not a problem as such Validate.isTrue(Double.doubleToLongBits(forwards[j]) == Double.doubleToLongBits(nodes[j]), "inconsistent nodes"); p = 0.5; // Arbitrary as nodes are degenerate } else { p = (forwards[j] - nodes[j]) / diff; } tree[i - 1][j] = new BinomialTreeNode<>(spots[j], p); } spots = nodes; } // fill out the final column of nodes - probability is set to zero tree[nSteps] = new BinomialTreeNode[nSteps + 1]; for (int j = 0; j <= nSteps; j++) { tree[nSteps][j] = new BinomialTreeNode<>(spots[j], 0.0); } return new RecombiningBinomialTree<>(tree); } protected abstract double[] getForwards(final double[] spots, final T data, final double t, final double dt); protected abstract double getNextHigherNode(final double dt, final double sigma, final double forward, final double lowerNode); protected abstract double getNextLowerNode(final double dt, final double sigma, final double forward, final double higherNode); protected abstract DoublesPair getCentralNodePair(final double dt, final double sigma, final double forward, final double centreLevel); /** * Builds a tree of option prices * @param definition Option Definition * @param data OptionDataBundle * @param assetTree A previously built asset price tree * @return a tree of option prices */ @SuppressWarnings("unchecked") public RecombiningBinomialTree<BinomialTreeNode<Double>> buildOptionPriceTree(final OptionDefinition definition, final T data, final RecombiningBinomialTree<BinomialTreeNode<Double>> assetTree) { final int nSteps = assetTree.getDepth() - 1; final BinomialTreeNode<Double>[][] tree = new BinomialTreeNode[nSteps + 1][]; final OptionPayoffFunction<T> payoffFunction = definition.getPayoffFunction(); final OptionExerciseFunction<T> exerciseFunction = definition.getExerciseFunction(); final YieldAndDiscountCurve yieldCurve = data.getInterestRateCurve(); double spot; tree[nSteps] = new BinomialTreeNode[nSteps + 1]; for (int j = 0; j <= nSteps; j++) { spot = assetTree.getNode(nSteps, j).getValue(); final double value = payoffFunction.getPayoff((T) data.withSpot(spot), 0.0); tree[nSteps][j] = new BinomialTreeNode<>(value, 0.0); // no need to set the probabilities } final double maturity = definition.getTimeToExpiry(data.getDate()); final double dt = maturity / nSteps; double t = maturity; for (int i = nSteps - 1; i >= 0; i--) { t -= dt; final double df = yieldCurve.getDiscountFactor(t + dt) / yieldCurve.getDiscountFactor(t); tree[i] = new BinomialTreeNode[i + 1]; for (int j = 0; j <= i; j++) { final BinomialTreeNode<Double> node = assetTree.getNode(i, j); final double p = node.getUpProbability(); spot = node.getValue(); final double optionValue = df * (p * tree[i + 1][j + 1].getValue() + (1 - p) * tree[i + 1][j].getValue()); final T newData = (T) data.withSpot(spot); final double value = exerciseFunction.shouldExercise(newData, optionValue) ? payoffFunction.getPayoff(newData, optionValue) : optionValue; tree[i][j] = new BinomialTreeNode<>(value, 0.0); } } return new RecombiningBinomialTree<>(tree); } }