/** * Copyright (C) 2013 - 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 java.util.Arrays; import com.opengamma.util.ArgumentChecker; /** * */ public class BermudanOptionFunctionProvider extends OptionFunctionProvider1D { private int _nTimes; private double[] _exerciseTimes; private int[] _exerciseSteps; /** * @param strike Strike price * @param timeToExpiry Time to expiry * @param steps Number of steps * @param isCall True if call, false if put * @param exerciseTimes a set of dates on which option can be exercised */ public BermudanOptionFunctionProvider(final double strike, final double timeToExpiry, final int steps, final boolean isCall, final double[] exerciseTimes) { super(strike, timeToExpiry, steps, isCall); ArgumentChecker.notNull(exerciseTimes, "exerciseDates"); _nTimes = exerciseTimes.length; ArgumentChecker.isTrue(steps + 1 >= _nTimes, "Number of steps is not large enough"); for (int i = 0; i < _nTimes; ++i) { ArgumentChecker.isTrue(exerciseTimes[i] >= 0., "exerciseDates should be non-negative"); ArgumentChecker.isTrue(exerciseTimes[i] <= timeToExpiry, "exerciseDates should be less than timeToExpiry"); } _exerciseTimes = Arrays.copyOf(exerciseTimes, _nTimes); Arrays.sort(_exerciseTimes); _exerciseSteps = timesToSteps(steps, timeToExpiry / steps); } @Override public double[] getPayoffAtExpiry(final double assetPrice, final double downFactor, final double upOverDown) { final double strike = getStrike(); final int nSteps = getNumberOfSteps(); final int nStepsP = nSteps + 1; final double sign = getSign(); final double[] values = new double[nStepsP]; double priceTmp = assetPrice * Math.pow(downFactor, nSteps); for (int i = 0; i < nStepsP; ++i) { values[i] = Math.max(sign * (priceTmp - strike), 0.); priceTmp *= upOverDown; } return values; } @Override public double[] getNextOptionValues(final double discount, final double upProbability, final double downProbability, final double[] values, final double baseAssetPrice, final double sumCashDiv, final double downFactor, final double upOverDown, final int steps) { final double strike = getStrike(); final int nStepsP = steps + 1; final double[] res = new double[nStepsP]; final boolean exercise = checkExercise(steps); if (exercise) { final double sign = getSign(); double assetPrice = baseAssetPrice * Math.pow(downFactor, steps); for (int j = 0; j < nStepsP; ++j) { res[j] = Math.max(discount * (upProbability * values[j + 1] + downProbability * values[j]), sign * (assetPrice + sumCashDiv - strike)); assetPrice *= upOverDown; } } else { for (int j = 0; j < nStepsP; ++j) { res[j] = discount * (upProbability * values[j + 1] + downProbability * values[j]); } } return res; } @Override public double[] getPayoffAtExpiryTrinomial(final double assetPrice, final double downFactor, final double middleOverDown) { final double strike = getStrike(); final int nSteps = getNumberOfSteps(); final int nNodes = 2 * getNumberOfSteps() + 1; final double sign = getSign(); final double[] values = new double[nNodes]; double priceTmp = assetPrice * Math.pow(downFactor, nSteps); for (int i = 0; i < nNodes; ++i) { values[i] = Math.max(sign * (priceTmp - strike), 0.); priceTmp *= middleOverDown; } return values; } @Override public double[] getNextOptionValues(final double discount, final double upProbability, final double middleProbability, final double downProbability, final double[] values, final double baseAssetPrice, final double sumCashDiv, final double downFactor, final double middleOverDown, final int steps) { final double strike = getStrike(); final int nNodes = 2 * steps + 1; final double[] res = new double[nNodes]; final boolean exercise = checkExercise(steps); if (exercise) { final double sign = getSign(); double assetPrice = baseAssetPrice * Math.pow(downFactor, steps); for (int j = 0; j < nNodes; ++j) { res[j] = Math.max(discount * (upProbability * values[j + 2] + middleProbability * values[j + 1] + downProbability * values[j]), sign * (assetPrice + sumCashDiv - strike)); assetPrice *= middleOverDown; } } else { for (int j = 0; j < nNodes; ++j) { res[j] = discount * (upProbability * values[j + 2] + middleProbability * values[j + 1] + downProbability * values[j]); } } return res; } /** * Access number of exercise times * @return _nTimes */ public int getNumberOfExerciseTimes() { return _nTimes; } /** * Access exercise times * @return _exerciseTimes */ public double[] getExerciseTimes() { return _exerciseTimes; } /** * Access exercise steps * @return _exerciseSteps */ public int[] getExerciseSteps() { return _exerciseSteps; } private boolean checkExercise(final int currentStep) { for (int i = 0; i < _nTimes; ++i) { if (currentStep == _exerciseSteps[i]) { return true; } } return false; } private int[] timesToSteps(final int nSteps, final double dt) { final int[] steps = new int[_nTimes]; int j = 0; for (int i = 0; i < nSteps + 1; ++i) { final double currentTime = dt * i; if (currentTime >= _exerciseTimes[j]) { final double ref1 = currentTime - _exerciseTimes[j]; final double ref2 = dt - ref1; steps[j] = ref1 <= ref2 ? i : i - 1; if (j != 0) { ArgumentChecker.isFalse(steps[j] == steps[j - 1], "Number of steps is not large enough"); } ++j; } if (j == _nTimes) { i = nSteps; } } return steps; } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + Arrays.hashCode(_exerciseSteps); result = prime * result + Arrays.hashCode(_exerciseTimes); result = prime * result + _nTimes; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (!(obj instanceof BermudanOptionFunctionProvider)) { return false; } BermudanOptionFunctionProvider other = (BermudanOptionFunctionProvider) obj; if (!Arrays.equals(_exerciseTimes, other._exerciseTimes)) { return false; } return true; } }