Package com.opengamma.analytics.financial.equity.variance.pricing

Source Code of com.opengamma.analytics.financial.equity.variance.pricing.EquityVarianceSwapMonteCarloCalculator$MonteCarloPath

/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.equity.variance.pricing;

import java.util.Arrays;

import cern.jet.random.engine.MersenneTwister64;
import cern.jet.random.engine.RandomEngine;

import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceStrike;
import com.opengamma.analytics.math.FunctionUtils;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.util.ArgumentChecker;

/**
* Monte-Carlo calculator to price a variance swap in the presence of discrete dividends. <p>
* <b>Note</b> this is primarily to test other numerical methods
*/
public class EquityVarianceSwapMonteCarloCalculator {
  /** The number of simulation variables - spot and realized variance with and without dividends correction */
  private static final int N_SIM_VARIABLES = 3;
  /** The number of days per year */
  private static final int DAYS_PER_YEAR = 252;

  /** Whether to use antithetic variables */
  private final boolean _useAntithetics;
  /** Whether to calculate the variance of the result */
  private final boolean _calculateVariance;
  /** Provides normally-distributed random numbers */
  private final NormalDistribution _norm;

  /**
   * Constructor taking a seed for the random number generator. The calculator is set up to use antithetic variables
   * and calculate the variance of the result.
   * @param seed The seed
   */
  public EquityVarianceSwapMonteCarloCalculator(final int seed) {
    _useAntithetics = true;
    _calculateVariance = true;
    final RandomEngine random = new MersenneTwister64(seed);
    _norm = new NormalDistribution(0, 1.0, random);
  }

  /**
   * @param seed The seed
   * @param useAntithetics true if antithetic variables are to be used
   * @param calculateVariance true if the variance of the result is to be calculated
   */
  public EquityVarianceSwapMonteCarloCalculator(final int seed, final boolean useAntithetics, final boolean calculateVariance) {
    _useAntithetics = useAntithetics;
    _calculateVariance = calculateVariance;
    final RandomEngine random = new MersenneTwister64(seed);
    _norm = new NormalDistribution(0, 1.0, random);
  }

  /**
   * @param spot The spot value, not negative
   * @param discountCurve The discount curve, not null
   * @param dividends The dividends, not null
   * @param expiry The time to expiry in years, not negative
   * @param localVol A local volatility surface parameterised by strike, not null
   * @param nSims The number of simulations to run, not negative
   * @return An array containing the final value of spot, realized variance with dividends, realized variance without dividends
   * and the variance of these values of requested.
   */
  public double[] solve(final double spot, final YieldAndDiscountCurve discountCurve, final AffineDividends dividends,
      final double expiry, final LocalVolatilitySurfaceStrike localVol, final int nSims) {

    ArgumentChecker.notNull(dividends, "null dividends");
    ArgumentChecker.notNegative(expiry, "negative expiry");
    ArgumentChecker.notNegative(spot, "negative spot");
    ArgumentChecker.notNull(discountCurve, "null discountCurve");
    ArgumentChecker.notNull(localVol, "null localVol");
    ArgumentChecker.notNegative(nSims, "negative nSims");

    final MonteCarloPath mc = new MonteCarloPath(dividends, expiry, spot, discountCurve, localVol);
    return mc.runMC(nSims, _useAntithetics, _calculateVariance);

  }

  /**
   * Monte-Carlo generator
   */
  private class MonteCarloPath {
    /** The affine dividends */
    private final AffineDividends _dividends;
    /** The spot */
    private final double _spot;
    /** The steps */
    private final int[] _steps;
    /** The number of days to expiry */
    private final int _nSteps;
    /** The number of dividends before expiry */
    private final int _nDivs;
    /** The local volatility surface */
    private final LocalVolatilitySurfaceStrike _localVol;
    /** The time step (one day) */
    private final double _dt;
    /** The square root of the time step */
    private final double _rootDt;
    /** The drift at each time step */
    private final double[] _drift;

    public MonteCarloPath(final AffineDividends dividends, final double expiry, final double spot,
        final YieldAndDiscountCurve discountCurve, final LocalVolatilitySurfaceStrike localVol) {

      _dividends = dividends;
      _spot = spot;
      _localVol = localVol;

      final int nDivs = dividends.getNumberOfDividends();

      _dt = 1.0 / DAYS_PER_YEAR;
      _rootDt = Math.sqrt(_dt);
      _nSteps = (int) Math.ceil((expiry * DAYS_PER_YEAR)); //Effectively move expiry to end of day
      _drift = new double[_nSteps];
      final double[] logP = new double[_nSteps + 1];
      logP[0] = 0.0;
      for (int i = 0; i < _nSteps; i++) {
        final double t = (i + 1) * _dt;
        logP[i + 1] = -discountCurve.getInterestRate(t) * t;
        _drift[i] = -(logP[i + 1] - logP[i]) / _dt; //forward difference to get drift
      }

      int nDivsBeforeExpiry = 0;
      int totalSteps = 0;
      final int[] steps = new int[nDivs + 1];

      if (nDivs == 0 || dividends.getTau(0) > expiry) { //no dividends or first dividend after option expiry
        steps[0] = (int) (Math.ceil(expiry * DAYS_PER_YEAR));
        totalSteps = steps[0];
      } else {
        steps[0] = (int) (Math.ceil(dividends.getTau(0) * DAYS_PER_YEAR)) - 1; //Effectively move dividend payment to end of day, and take steps up to the end of the day before
        totalSteps += steps[0] + 1;
        nDivsBeforeExpiry++;
        for (int i = 1; i < nDivs; i++) {
          if (dividends.getTau(i) > expiry) { //if dividend after expiry, step steps up to expiry and do not consider any more dividends
            steps[i] = ((int) Math.ceil(expiry * DAYS_PER_YEAR)) - totalSteps;
            totalSteps += steps[i];
            break;
          }
          steps[i] = ((int) Math.ceil(dividends.getTau(i) * DAYS_PER_YEAR)) - totalSteps - 1;
          totalSteps += steps[i] + 1;
          nDivsBeforeExpiry++;
        }

        if (dividends.getTau(nDivs - 1) < expiry) {
          steps[nDivs] = ((int) Math.ceil(expiry * DAYS_PER_YEAR)) - totalSteps;
          totalSteps += steps[nDivs];
        }
      }

      //check
      ArgumentChecker.isTrue(totalSteps == _nSteps, "got the steps wrong");

      //only care about the dividends that occur before expiry
      _steps = Arrays.copyOfRange(steps, 0, nDivsBeforeExpiry + 1);
      _nDivs = nDivsBeforeExpiry;

    }

    public double[] runMC(final int nSims, final boolean useAntithetics, final boolean calErrors) {

      final int nVar = calErrors ? 2 * N_SIM_VARIABLES : N_SIM_VARIABLES;
      final double[] res = new double[nVar];
      double[] temp;
      for (int i = 0; i < nSims; i++) {
        final double[] z = getNormals();
        temp = runPath(z);
        for (int j = 0; j < N_SIM_VARIABLES; j++) {
          res[j] += temp[j];
        }
        if (calErrors) {
          for (int j = 0; j < N_SIM_VARIABLES; j++) {
            res[j + N_SIM_VARIABLES] += temp[j] * temp[j];
          }
        }
        if (useAntithetics) {
          for (int k = 0; k < _nSteps; k++) {
            z[k] *= -1.0;
          }
          temp = runPath(z);
          for (int j = 0; j < N_SIM_VARIABLES; j++) {
            res[j] += temp[j];
          }
          if (calErrors) {
            for (int j = 0; j < N_SIM_VARIABLES; j++) {
              res[j + N_SIM_VARIABLES] += temp[j] * temp[j];
            }
          }
        }
      }
      final int n = useAntithetics ? 2 * nSims : nSims;
      for (int j = 0; j < N_SIM_VARIABLES; j++) {
        res[j] /= n;
      }
      if (calErrors) {
        for (int j = 0; j < N_SIM_VARIABLES; j++) {
          res[j + N_SIM_VARIABLES] = (res[j + N_SIM_VARIABLES] - n * FunctionUtils.square(res[j])) / (n - 1) / n;
        }
      }

      return res;
    }

    /**
     *
     * @param z Set of iid standard normal random variables
     * @return The final value of spot and the realized variance with and without dividends correction
     */
    public double[] runPath(final double[] z) {

      double sOld = _spot; //previous value of stock
      double s = 0; //current value of stock
      double sTotOld = _spot; //previous value of total returns process
      double sTot = 0; //current value of total returns process
      double rv1 = 0.0; //Accumulator of realised variance (with correction made for dividends)
      double rv2 = 0.0; //Accumulator of realised variance (without correction made for dividends)
      double t = 0.0; //current time
      double vol; //value of local vol at start of step
      double mu; //The drift at start of step
      double ret; //The daily return
      double temp;

      int tSteps = 0;
      for (int k = 0; k < _nDivs; k++) {
        final int steps = _steps[k];
        //simulate the continuous path between dividends
        for (int i = 0; i < steps; i++) {
          vol = _localVol.getVolatility(t, sOld);
          mu = _drift[tSteps];
          //this Euler step is exact if the volatility and drift are constant, otherwise it is subject to discretisation error
          ret = (mu - vol * vol / 2) * _dt + vol * _rootDt * z[tSteps];
          temp = Math.exp(ret);
          s = sOld * temp;
          sTot = sTotOld * temp;
          temp = ret * ret;
          rv1 += temp;
          rv2 += temp;
          sOld = s;
          sTotOld = sTot;
          t += _dt;
          tSteps++;
        }

        //simulate the path on dividend day
        //The dividend payment is effectively moved to the end of the day
        vol = _localVol.getVolatility(t, sOld);
        mu = _drift[tSteps];
        temp = Math.exp((mu - vol * vol / 2) * _dt + vol * _rootDt * z[tSteps]);
        final double sm = sOld * temp; //the stock price immediately before the dividend payment
        sTot = sTotOld * temp;
        s = sm * (1 - _dividends.getBeta(k)) - _dividends.getAlpha(k);
        rv1 += FunctionUtils.square(Math.log(sm / sOld));
        rv2 += FunctionUtils.square(Math.log(s / sOld));
        sOld = s;
        sTotOld = sTot;
        t += _dt;
        tSteps++;
      }

      //simulate the remaining continuous path from the last dividend to the expiry
      final int steps = _steps[_nDivs];
      //simulate the continuous path between dividends
      for (int i = 0; i < steps; i++) {
        vol = _localVol.getVolatility(t, sOld);
        mu = _drift[tSteps];
        ret = (mu - vol * vol / 2) * _dt + vol * _rootDt * z[tSteps]; //this Euler step will be subject to discretisation error
        temp = Math.exp(ret);
        sTot = sTotOld * temp;
        s = sOld * temp;
        rv1 += ret * ret;
        rv2 += ret * ret;
        sOld = s;
        sTotOld = sTot;
        t += _dt;
        tSteps++;
      }

      //correct for mean and bias
      rv1 -= FunctionUtils.square(Math.log(sTot / _spot)) / _nSteps;
      rv2 -= FunctionUtils.square(Math.log(s / _spot)) / _nSteps;
      final double biasCorr = ((double) DAYS_PER_YEAR) / (_nSteps - 1);
      rv1 *= biasCorr;
      rv2 *= biasCorr;

      return new double[] {s, rv1, rv2 };
    }

    @SuppressWarnings("synthetic-access")
    private double[] getNormals() {
      final double[] z = new double[_nSteps];
      for (int i = 0; i < _nSteps; i++) {
        z[i] = _norm.nextRandom();
      }
      return z;
    }

  }
}
TOP

Related Classes of com.opengamma.analytics.financial.equity.variance.pricing.EquityVarianceSwapMonteCarloCalculator$MonteCarloPath

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.