com.opengamma.strata.pricer.impl.option.BlackOneTouchCashPriceFormulaRepository.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.strata.pricer.impl.option.BlackOneTouchCashPriceFormulaRepository.java

Source

/**
 * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.impl.option;

import com.google.common.math.DoubleMath;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.math.impl.statistics.distribution.NormalDistribution;
import com.opengamma.strata.math.impl.statistics.distribution.ProbabilityDistribution;
import com.opengamma.strata.product.option.SimpleConstantContinuousBarrier;

/**
 * The price function to compute the price of one-touch or no-touch (cash-or-nothing) option in the Black world.
 * <p>
 * This function prices one-touch/no-touch option, where the cash payment can occur at hit for a one-touch option, and 
 * at expiry for a no-touch option.
 * Reference: E. G. Haug (2007) The complete guide to Option Pricing Formulas. Mc Graw Hill. Section 4.19.5.
 */
public class BlackOneTouchCashPriceFormulaRepository {

    /**
     * The normal distribution implementation used in the pricing.
     */
    private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
    /**
     * Small parameter.
     */
    private static final double SMALL = 1.0e-6;

    /**
     * Computes the price of a one-touch/no-touch option.
     * 
     * @param spot  the spot 
     * @param timeToExpiry  the time to expiry 
     * @param costOfCarry  the cost of carry 
     * @param rate  the interest rate
     * @param lognormalVol  the lognormal volatility
     * @param barrier  the barrier
     * @return the price
     */
    public double price(double spot, double timeToExpiry, double costOfCarry, double rate, double lognormalVol,
            SimpleConstantContinuousBarrier barrier) {

        ArgChecker.notNull(barrier, "barrier");
        boolean isKnockIn = barrier.getKnockType().isKnockIn();
        boolean isDown = barrier.getBarrierType().isDown();
        double h = barrier.getBarrierLevel();
        ArgChecker.isFalse(isDown && spot <= barrier.getBarrierLevel(),
                "The Data is not consistent with an alive barrier (DOWN and spot<=barrier).");
        ArgChecker.isFalse(!isDown && spot >= barrier.getBarrierLevel(),
                "The Data is not consistent with an alive barrier (UP and spot>=barrier).");
        double eta = isDown ? 1 : -1;
        double df2 = Math.exp(-rate * timeToExpiry);
        double lognormalVolSq = lognormalVol * lognormalVol;
        double lognormalVolT = lognormalVol * Math.sqrt(timeToExpiry);
        if (DoubleMath.fuzzyEquals(Math.min(timeToExpiry, lognormalVolSq), 0d, SMALL)) {
            return isKnockIn ? 0d : df2;
        }
        double mu = (costOfCarry - 0.5 * lognormalVolSq) / lognormalVolSq;
        double lambda = Math.sqrt(mu * mu + 2 * rate / lognormalVolSq);
        double m1 = lognormalVolT * (1 + mu);
        double x2 = Math.log(spot / h) / lognormalVolT + m1;
        double y2 = Math.log(h / spot) / lognormalVolT + m1;
        double z = Math.log(h / spot) / lognormalVolT + lambda * lognormalVolT;
        double xE = isKnockIn ? getF(spot, z, lognormalVolT, h, mu, lambda, eta)
                : getE(spot, df2, x2, y2, lognormalVolT, h, mu, eta);
        return xE;
    }

    /**
     * Computes the price and derivatives of a one-touch/no-touch option.
     * <p>
     * The derivatives are [0] spot, [1] rate, [2] cost-of-carry, [3] volatility, [4] timeToExpiry, [5] spot twice.
     * 
     * @param spot  the spot 
     * @param timeToExpiry  the time to expiry 
     * @param costOfCarry  the cost of carry 
     * @param rate  the interest rate
     * @param lognormalVol  the lognormal volatility
     * @param barrier  the barrier
     * @return the price and derivatives
     */
    public ValueDerivatives priceAdjoint(double spot, double timeToExpiry, double costOfCarry, double rate,
            double lognormalVol, SimpleConstantContinuousBarrier barrier) {

        ArgChecker.notNull(barrier, "barrier");
        double[] derivatives = new double[6];
        boolean isKnockIn = barrier.getKnockType().isKnockIn();
        boolean isDown = barrier.getBarrierType().isDown();
        double h = barrier.getBarrierLevel();
        ArgChecker.isFalse(isDown && spot <= barrier.getBarrierLevel(),
                "The Data is not consistent with an alive barrier (DOWN and spot<=barrier).");
        ArgChecker.isFalse(!isDown && spot >= barrier.getBarrierLevel(),
                "The Data is not consistent with an alive barrier (UP and spot>=barrier).");

        double eta = isDown ? 1 : -1;
        double df2 = Math.exp(-rate * timeToExpiry);
        double lognormalVolSq = lognormalVol * lognormalVol;
        double lognormalVolT = lognormalVol * Math.sqrt(timeToExpiry);
        if (DoubleMath.fuzzyEquals(Math.min(timeToExpiry, lognormalVolSq), 0d, SMALL)) {
            if (isKnockIn) {
                return ValueDerivatives.of(0d, DoubleArray.filled(6));
            }
            double price = df2;
            derivatives[1] = -timeToExpiry * price;
            derivatives[4] = -rate * price;
            return ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives));
        }
        double mu = (costOfCarry - 0.5 * lognormalVolSq) / lognormalVolSq;
        double lambda = Math.sqrt(mu * mu + 2d * rate / lognormalVolSq);
        double m1 = lognormalVolT * (1d + mu);
        double x2 = Math.log(spot / h) / lognormalVolT + m1;
        double y2 = Math.log(h / spot) / lognormalVolT + m1;
        double z = Math.log(h / spot) / lognormalVolT + lambda * lognormalVolT;
        double[] eDerivFirst = new double[6];
        double[] eDerivSecond = new double[6];
        double[] fDerivFirst = new double[5];
        double[] fDerivSecond = new double[5];
        double price = isKnockIn
                ? getFAdjoint(spot, z, lognormalVolT, h, mu, lambda, eta, fDerivFirst, fDerivSecond)
                : getEAdjoint(spot, df2, x2, y2, lognormalVolT, h, mu, eta, eDerivFirst, eDerivSecond);
        double zBar = 0.0;
        double y2Bar = 0.0;
        double x2Bar = 0.0;
        double zSqBar = 0.0;
        double y2SqBar = 0.0;
        double x2SqBar = 0.0;
        double zsBar = 0.0;
        double y2sBar = 0.0;
        double lambdaBar = 0.0;
        double muBar = 0.0;
        double lognormalVolTBar = 0.0;
        double df2Bar = 0.0;
        if (isKnockIn) {
            zBar = fDerivFirst[1];
            lambdaBar = fDerivFirst[4]; // only F has lambda dependence, which in turn is a function of mu, see muBar+= below
            muBar = fDerivFirst[3];
            lognormalVolTBar = fDerivFirst[2];
            derivatives[0] = fDerivFirst[0];
            zSqBar = fDerivSecond[1];
            zsBar = fDerivSecond[2];
            derivatives[5] = fDerivSecond[0];
        } else {
            y2Bar = eDerivFirst[3];
            x2Bar = eDerivFirst[2];
            muBar = eDerivFirst[5];
            lognormalVolTBar = eDerivFirst[4];
            df2Bar = eDerivFirst[1];
            derivatives[0] = eDerivFirst[0];
            y2SqBar = eDerivSecond[2];
            x2SqBar = eDerivSecond[1];
            y2sBar = eDerivSecond[3];
            derivatives[5] = eDerivSecond[0];
        }
        double dxyds = 1d / spot / lognormalVolT;
        double m1Bar = x2Bar + y2Bar;
        muBar += +lognormalVolT * m1Bar + mu / lambda * lambdaBar;
        lognormalVolTBar += +(lambda - Math.log(h / spot) / (lognormalVolT * lognormalVolT)) * zBar
                - Math.log(h / spot) / (lognormalVolT * lognormalVolT) * y2Bar
                - Math.log(spot / h) / (lognormalVolT * lognormalVolT) * x2Bar + (1 + mu) * m1Bar;
        double lognormalVolSqBar = -costOfCarry / (lognormalVolSq * lognormalVolSq) * muBar
                - rate / (lognormalVolSq * lognormalVolSq) / lambda * lambdaBar;
        derivatives[0] += dxyds * x2Bar - dxyds * y2Bar - dxyds * zBar;
        derivatives[1] = -timeToExpiry * df2 * df2Bar + lambdaBar / lambda / lognormalVolSq;
        derivatives[2] = muBar / lognormalVolSq;
        derivatives[3] = 2d * lognormalVol * lognormalVolSqBar + Math.sqrt(timeToExpiry) * lognormalVolTBar;
        derivatives[4] = -rate * df2 * df2Bar + lognormalVolTBar * lognormalVolT * 0.5 / timeToExpiry;
        derivatives[5] += -dxyds * x2Bar / spot + dxyds * y2Bar / spot + dxyds * zBar / spot
                + dxyds * dxyds * x2SqBar + dxyds * dxyds * y2SqBar - 2d * dxyds * y2sBar + dxyds * dxyds * zSqBar
                - 2d * dxyds * zsBar;
        return ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives));
    }

    //-------------------------------------------------------------------------
    private double getE(double s, double df2, double x, double y, double lognormalVolT, double h, double mu,
            double eta) {

        return df2 * (NORMAL.getCDF(eta * (x - lognormalVolT))
                - Math.pow(h / s, 2d * mu) * NORMAL.getCDF(eta * (y - lognormalVolT)));
    }

    private double getF(double s, double z, double lognormalVolT, double h, double mu, double lambda, double eta) {

        return Math.pow(h / s, mu + lambda) * NORMAL.getCDF(eta * z)
                + Math.pow(h / s, mu - lambda) * NORMAL.getCDF(eta * (z - 2d * lambda * lognormalVolT));
    }

    //-------------------------------------------------------------------------
    // The firstDerivatives are [0] s, [1] df2, [2] x, [3] y, [4] lognormalVolT, [5] mu.
    // The second derivatives are [0] s twice, [1] x twice, [2] y twice, [3] s and y.
    private double getEAdjoint(double s, double df2, double x, double y, double lognormalVolT, double h, double mu,
            double eta, double[] firstDerivatives, double[] secondDerivatives) {

        double n1 = NORMAL.getCDF(eta * (x - lognormalVolT));
        double n2 = NORMAL.getCDF(eta * (y - lognormalVolT));
        double hsMu = Math.pow(h / s, 2 * mu);
        double e = df2 * (n1 - hsMu * n2);
        double n1df = NORMAL.getPDF(eta * (x - lognormalVolT));
        double n2df = NORMAL.getPDF(eta * (y - lognormalVolT));
        double hsMuBar = df2 * -n2;
        double n2Bar = df2 * -hsMu;
        double n1Bar = df2;
        firstDerivatives[0] = -2d * mu * hsMu / s * hsMuBar; // s
        firstDerivatives[1] = n1 - hsMu * n2; // df2;
        firstDerivatives[2] = n1df * eta * n1Bar; // x
        firstDerivatives[3] = n2df * eta * n2Bar; // y
        firstDerivatives[4] = n2df * -eta * n2Bar + n1df * -eta * n1Bar; // lognormalVolT
        firstDerivatives[5] = 2d * Math.log(h / s) * hsMu * hsMuBar; // mu
        secondDerivatives[0] = hsMu * hsMuBar * 2d * mu * (2d * mu + 1d) / (s * s);
        secondDerivatives[1] = -n1df * n1Bar * (x - lognormalVolT) * eta;
        secondDerivatives[2] = -n2df * n2Bar * (y - lognormalVolT) * eta;
        secondDerivatives[3] = -2d * mu * n2df * eta * n2Bar / s;
        return e;
    }

    // The firstDerivatives are [0] s, [1] z, [2] lognormalVolT, [3] mu, [4] lambda.
    // The second derivatives are [0] s twice, [1] z twice, [2] s and z.
    private double getFAdjoint(double s, double z, double lognormalVolT, double h, double mu, double lambda,
            double eta, double[] firstDerivatives, double[] secondDerivatives) {

        double n1 = NORMAL.getCDF(eta * z);
        double n2 = NORMAL.getCDF(eta * (z - 2 * lambda * lognormalVolT));
        double hsMuPLa = Math.pow(h / s, mu + lambda);
        double hsMuMLa = Math.pow(h / s, mu - lambda);
        double f = hsMuPLa * n1 + hsMuMLa * n2;
        double fBar = 1.0;
        double n1df = NORMAL.getPDF(eta * z);
        double n2df = NORMAL.getPDF(eta * (z - 2 * lambda * lognormalVolT));
        double hsMuPLaBar = n1 * fBar;
        double hsMuMLaBar = n2 * fBar;
        double n2Bar = hsMuMLa * fBar;
        double n1Bar = hsMuPLa * fBar;
        firstDerivatives[0] = -(mu + lambda) * hsMuPLa / s * hsMuPLaBar - (mu - lambda) * hsMuMLa / s * hsMuMLaBar; //s
        firstDerivatives[1] = n1df * eta * n1Bar + n2df * eta * n2Bar; // z
        firstDerivatives[2] = -n2df * eta * 2 * lambda * n2Bar; //lognormalVolT
        firstDerivatives[3] = hsMuPLa * Math.log(h / s) * hsMuPLaBar + hsMuMLa * Math.log(h / s) * hsMuMLaBar; // mu
        firstDerivatives[4] = hsMuPLa * Math.log(h / s) * hsMuPLaBar - hsMuMLa * Math.log(h / s) * hsMuMLaBar; // lambda
        secondDerivatives[0] = hsMuPLa * hsMuPLaBar * (mu + lambda) * (mu + lambda + 1d) / (s * s)
                + hsMuMLa * hsMuMLaBar * (mu - lambda) * (mu - lambda + 1d) / (s * s);
        secondDerivatives[1] = -z * n1df * eta * n1Bar - (z - 2 * lambda * lognormalVolT) * n2df * eta * n2Bar;
        secondDerivatives[2] = -n1df * n1Bar * (mu + lambda) * eta / s - n2df * n2Bar * (mu - lambda) * eta / s;
        return f;
    }
}