com.opengamma.analytics.financial.model.finitedifference.applications.BarrierOptionPricer.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.analytics.financial.model.finitedifference.applications.BarrierOptionPricer.java

Source

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

import org.apache.commons.lang.Validate;

import com.opengamma.analytics.financial.model.finitedifference.BoundaryCondition;
import com.opengamma.analytics.financial.model.finitedifference.ConvectionDiffusionPDE1DCoefficients;
import com.opengamma.analytics.financial.model.finitedifference.ConvectionDiffusionPDE1DStandardCoefficients;
import com.opengamma.analytics.financial.model.finitedifference.DirichletBoundaryCondition;
import com.opengamma.analytics.financial.model.finitedifference.ExponentialMeshing;
import com.opengamma.analytics.financial.model.finitedifference.HyperbolicMeshing;
import com.opengamma.analytics.financial.model.finitedifference.MeshingFunction;
import com.opengamma.analytics.financial.model.finitedifference.PDE1DDataBundle;
import com.opengamma.analytics.financial.model.finitedifference.PDEGrid1D;
import com.opengamma.analytics.financial.model.finitedifference.PDEResults1D;
import com.opengamma.analytics.financial.model.finitedifference.ThetaMethodFiniteDifference;
import com.opengamma.analytics.financial.model.option.definition.Barrier;
import com.opengamma.analytics.financial.model.option.definition.Barrier.BarrierType;
import com.opengamma.analytics.financial.model.option.definition.Barrier.KnockType;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.BlackBarrierPriceFunction;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.SurfaceArrayUtils;
import com.opengamma.analytics.math.function.Function1D;

/**
 * 
 */
public class BarrierOptionPricer {

    private static final InitialConditionsProvider ICP = new InitialConditionsProvider();
    private static final PDE1DCoefficientsProvider PDE = new PDE1DCoefficientsProvider();
    private static final ThetaMethodFiniteDifference SOLVER = new ThetaMethodFiniteDifference();

    private static final int DEFAULT_XNODES = 100;
    private static final int DEFAULT_TNODES = 50;
    private static final double DEFAULT_LAMBDA = 0.0;
    private static final double DEFAULT_BUNCHING = 1.0;
    private static final double DEFAULT_Z = 2.0;

    private final int _nTNodes;
    private final int _nXNodes;
    private final double _lambda;
    private final double _bunching;
    private final double _z = DEFAULT_Z;

    public BarrierOptionPricer() {
        _nTNodes = DEFAULT_TNODES;
        _nXNodes = DEFAULT_XNODES;
        _lambda = DEFAULT_LAMBDA;
        _bunching = DEFAULT_BUNCHING;
    }

    public BarrierOptionPricer(final int numXNodes, final int numTNodes, final double lambda,
            final double bunching) {

        _nXNodes = numXNodes;
        _nTNodes = numTNodes;
        _lambda = lambda;
        _bunching = bunching;

    }

    /**
     * Computes the price of a barrier option in the Black world.
     * @param option The underlying European vanilla option.
     * @param barrier The barrier.
     * @param rebate The rebate. This is paid <b>immediately</b> if the knock-out barrier is hit and at expiry if the knock-in barrier is not hit
     * @param spot The spot price.
     * @param costOfCarry The cost of carry (i.e. the forward = spot*exp(costOfCarry*T) )
     * @param rate The interest rate.
     * @param sigma The Black volatility.
     * @return The price.
     */
    public double getPrice(final EuropeanVanillaOption option, final Barrier barrier, final double rebate,
            final double spot, final double costOfCarry, final double rate, final double sigma) {
        Validate.notNull(option, "option");
        Validate.notNull(barrier, "barrier");
        final boolean isKnockIn = (barrier.getKnockType() == KnockType.IN);
        final boolean isDown = (barrier.getBarrierType() == BarrierType.DOWN);

        //in these pathological cases the barrier is hit immediately so the value is just from the rebate (knock-out) or a European option (knock-in)
        if (isDown && spot <= barrier.getBarrierLevel() || !isDown && spot >= barrier.getBarrierLevel()) {
            if (isKnockIn) {
                return blackPrice(spot, option.getStrike(), option.getTimeToExpiry(), rate, costOfCarry, sigma,
                        option.isCall());
            }
            return rebate;
        }
        if (isKnockIn) {
            return inBarrier(spot, barrier.getBarrierLevel(), option.getStrike(), option.getTimeToExpiry(), rate,
                    costOfCarry, sigma, option.isCall(), rebate);
        }
        return outBarrier(spot, barrier.getBarrierLevel(), option.getStrike(), option.getTimeToExpiry(), rate,
                costOfCarry, sigma, option.isCall(), rebate);
    }

    /**
     * Computes the price of a one-touch out barrier option in the Black-Scholes world by solving the BS PDE on a finite difference grid. If a barrier is hit at any time before expiry,
     * the option is cancelled (knocked-out) and a rebate (which is often zero) is paid <b>immediately</b>. If the barrier is not hit, then a normal European option payment is made. <p>
     * If the barrier is above the spot it is assumed to be an up-and-out barrier (otherwise it would expire immediately) otherwise it is a down-and-out barrier
     * As there are exact formulae for this case (see {@link BlackBarrierPriceFunction}), this is purely for testing purposes.
     * @param spot The current (i.e. option price time) of the underlying
     * @param barrierLevel The barrier level
     * @param strike The strike of the European option
     * @param expiry The expiry of the option
     * @param rate The interest rate.
     * @param carry The cost-of-carry (i.e. the forward = spot*exp(costOfCarry*T) )
     * @param vol The Black volatility.
     * @param isCall true for call
     * @param rebate The rebate amount.
     * @return The price.
     */
    public double outBarrier(final double spot, final double barrierLevel, final double strike, final double expiry,
            final double rate, final double carry, final double vol, final boolean isCall, final double rebate) {

        final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall);
        final ConvectionDiffusionPDE1DStandardCoefficients pde = PDE.getBlackScholes(rate, rate - carry, vol);
        final boolean isUp = barrierLevel > spot;

        final double adj = 0.0; // _lambda == 0 ? ZETA * vol * Math.sqrt(expiry / (_nTNodes - 1)) : 0.0;

        double sMin;
        double sMax;
        BoundaryCondition lower;
        BoundaryCondition upper;
        if (isUp) {
            sMin = 0.0;
            sMax = barrierLevel * Math.exp(-adj); //bring the barrier DOWN slightly to adjust for discrete monitoring
            if (isCall) {
                lower = new DirichletBoundaryCondition(0.0, sMin);
            } else {
                final Function1D<Double, Double> lowerValue = new Function1D<Double, Double>() {
                    @Override
                    public Double evaluate(final Double tau) {
                        return Math.exp(-rate * tau) * strike;
                    }
                };
                lower = new DirichletBoundaryCondition(lowerValue, sMin);
            }
            upper = new DirichletBoundaryCondition(rebate, sMax);
        } else {
            sMin = barrierLevel * Math.exp(adj); //bring the barrier UP slightly to adjust for discrete monitoring
            sMax = spot * Math.exp(_z * Math.sqrt(expiry));
            lower = new DirichletBoundaryCondition(rebate, sMin);

            if (isCall) {
                final Function1D<Double, Double> upperValue = new Function1D<Double, Double>() {
                    @Override
                    public Double evaluate(final Double tau) {
                        return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike);
                    }
                };
                upper = new DirichletBoundaryCondition(upperValue, sMax);
            } else {
                upper = new DirichletBoundaryCondition(0.0, sMax);
            }
        }

        final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda);
        final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching);
        final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh);
        final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(
                pde, intCon, lower, upper, grid);

        final PDEResults1D res = SOLVER.solve(pdeData);
        //for now just do linear interpolation on price. TODO replace this with something more robust
        final double[] xNodes = grid.getSpaceNodes();

        final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot);
        final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]);
        return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1);
    }

    /**
     * Computes the price of a one-touch in barrier option in the Black-Scholes world by solving the BS PDE on a finite difference grid. If a barrier is hit at any time before expiry,
     * the option becomes a simple European (call or put). If the barrier is not hit a rebate is paid at the option expiry<p>
     * If the barrier is above the spot it is assumed to be an up-and-in barrier  otherwise it is a down-and-in barrier
     * As there are exact formulae for this case (see {@link BlackBarrierPriceFunction}), this is purely for testing purposes.
     * @param spot The current (i.e. option price time) of the underlying
     * @param barrierLevel The barrier level
     * @param strike The strike of the European option
     * @param expiry The expiry of the option
     * @param rate The interest rate.
     * @param carry The cost-of-carry (i.e. the forward = spot*exp(costOfCarry*T) )
     * @param vol The Black volatility.
     * @param isCall true for call
     * @param rebate The rebate amount.
     * @return The price.
     */
    public double inBarrier(final double spot, final double barrierLevel, final double strike, final double expiry,
            final double rate, final double carry, final double vol, final boolean isCall, final double rebate) {
        final double outPrice = outBarrierSpecial(spot, barrierLevel, strike, expiry, rate, carry, vol, isCall,
                rebate);
        final double bsPrice = blackPrice(spot, strike, expiry, rate, carry, vol, isCall);
        return bsPrice + Math.exp(-rate * expiry) * rebate - outPrice;
    }

    /**
     * Computes the price of a one-touch out barrier option in the Black-Scholes world assuming the rebate is paid at the option expiry. This is NOT the case for a out barrier,
     * but is for an in, and as we must price an in barrier and a European option plus a bond (the rebate) minus an out, we need this special case.
     * @param spot The current (i.e. option price time) of the underlying
     * @param barrierLevel The barrier level
     * @param strike The strike of the European option
     * @param expiry The expiry of the option
     * @param rate The interest rate.
     * @param carry The cost-of-carry (i.e. the forward = spot*exp(costOfCarry*T) )
     * @param vol The Black volatility.
     * @param isCall true for call
     * @param rebate The rebate amount.
     * @return The price.
     */
    protected double outBarrierSpecial(final double spot, final double barrierLevel, final double strike,
            final double expiry, final double rate, final double carry, final double vol, final boolean isCall,
            final double rebate) {

        final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall);
        final ConvectionDiffusionPDE1DStandardCoefficients pde = PDE.getBlackScholes(rate, rate - carry, vol);
        final boolean isUp = barrierLevel > spot;

        final Function1D<Double, Double> rebateValue = new Function1D<Double, Double>() {
            @Override
            public Double evaluate(final Double tau) {
                return rebate * Math.exp(-rate * tau);
            }
        };

        double sMin;
        double sMax;
        BoundaryCondition lower;
        BoundaryCondition upper;
        if (isUp) {
            sMin = 0.0;
            sMax = barrierLevel;
            if (isCall) {
                lower = new DirichletBoundaryCondition(0.0, sMin);
            } else {
                final Function1D<Double, Double> lowerValue = new Function1D<Double, Double>() {
                    @Override
                    public Double evaluate(final Double tau) {
                        return Math.exp(-rate * tau) * strike;
                    }
                };
                lower = new DirichletBoundaryCondition(lowerValue, sMin);
            }
            upper = new DirichletBoundaryCondition(rebateValue, sMax);
        } else {
            sMin = barrierLevel;
            sMax = spot * Math.exp(_z * Math.sqrt(expiry));
            lower = new DirichletBoundaryCondition(rebateValue, sMin);
            if (isCall) {
                final Function1D<Double, Double> upperValue = new Function1D<Double, Double>() {
                    @Override
                    public Double evaluate(final Double tau) {
                        return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike);
                    }
                };
                upper = new DirichletBoundaryCondition(upperValue, sMax);
            } else {
                upper = new DirichletBoundaryCondition(0.0, sMax);
            }
        }

        final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda);
        final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching);
        final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh);
        final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(
                pde, intCon, lower, upper, grid);

        final PDEResults1D res = SOLVER.solve(pdeData);
        //for now just do linear interpolation on price. TODO replace this with something more robust
        final double[] xNodes = grid.getSpaceNodes();
        final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot);
        final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]);
        return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1);
    }

    protected double blackPrice(final double spot, final double strike, final double expiry, final double rate,
            final double carry, final double vol, final boolean isCall) {

        final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall);
        final ConvectionDiffusionPDE1DStandardCoefficients pde = PDE.getBlackScholes(rate, rate - carry, vol);

        final double sMin = 0.0;
        final double sMax = spot * Math.exp(_z * Math.sqrt(expiry));
        BoundaryCondition lower;
        BoundaryCondition upper;
        if (isCall) {
            lower = new DirichletBoundaryCondition(0.0, sMin);
            final Function1D<Double, Double> upperValue = new Function1D<Double, Double>() {
                @Override
                public Double evaluate(final Double tau) {
                    return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike);
                }
            };
            upper = new DirichletBoundaryCondition(upperValue, sMax);
        } else {
            final Function1D<Double, Double> lowerValue = new Function1D<Double, Double>() {
                @Override
                public Double evaluate(final Double tau) {
                    return Math.exp(-rate * tau) * strike;
                }
            };
            lower = new DirichletBoundaryCondition(lowerValue, sMin);
            upper = new DirichletBoundaryCondition(0.0, sMax);
        }

        final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda);
        final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching);
        final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh);
        final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(
                pde, intCon, lower, upper, grid);

        final PDEResults1D res = SOLVER.solve(pdeData);
        //for now just do linear interpolation on price. TODO replace this with something more robust
        final double[] xNodes = grid.getSpaceNodes();
        final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot);
        final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]);
        return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1);
    }
}