com.opengamma.strata.pricer.swaption.HullWhiteSwaptionPhysicalProductPricer.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.strata.pricer.swaption.HullWhiteSwaptionPhysicalProductPricer.java

Source

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

import java.time.LocalDate;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.currency.Payment;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.DoubleArrayMath;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.math.impl.statistics.distribution.NormalDistribution;
import com.opengamma.strata.math.impl.statistics.distribution.ProbabilityDistribution;
import com.opengamma.strata.pricer.DiscountingPaymentPricer;
import com.opengamma.strata.pricer.impl.rate.swap.CashFlowEquivalentCalculator;
import com.opengamma.strata.pricer.model.HullWhiteOneFactorPiecewiseConstantParametersProvider;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.common.SettlementType;
import com.opengamma.strata.product.swap.NotionalExchange;
import com.opengamma.strata.product.swap.ResolvedSwap;
import com.opengamma.strata.product.swap.ResolvedSwapLeg;
import com.opengamma.strata.product.swap.SwapLegType;
import com.opengamma.strata.product.swaption.ResolvedSwaption;

/**
 * Pricer for swaption with physical settlement in Hull-White one factor model with piecewise constant volatility.
 * <p>
 * Reference: Henrard, M. "The Irony in the derivatives discounting Part II: the crisis", Wilmott Journal, 2010, 2, 301-316
 */
public class HullWhiteSwaptionPhysicalProductPricer {

    /**
     * Normal distribution function.
     */
    private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);

    /**
     * The small parameter.
     */
    private static final double SMALL = 1.0e-9;

    /**
     * Default implementation.
     */
    public static final HullWhiteSwaptionPhysicalProductPricer DEFAULT = new HullWhiteSwaptionPhysicalProductPricer(
            DiscountingPaymentPricer.DEFAULT);

    /**
     * Pricer for {@link Payment}.
     */
    private final DiscountingPaymentPricer paymentPricer;

    /**
     * Creates an instance.
     * 
     * @param paymentPricer  the pricer for {@link Payment}
     */
    public HullWhiteSwaptionPhysicalProductPricer(DiscountingPaymentPricer paymentPricer) {
        this.paymentPricer = ArgChecker.notNull(paymentPricer, "paymentPricer");
    }

    /**
     * Calculates the present value of the swaption product.
     * <p>
     * The result is expressed using the currency of the swapion.
     * 
     * @param swaption  the product
     * @param ratesProvider  the rates provider
     * @param hwProvider  the Hull-White model parameter provider
     * @return the present value
     */
    public CurrencyAmount presentValue(ResolvedSwaption swaption, RatesProvider ratesProvider,
            HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {

        validate(swaption, ratesProvider, hwProvider);
        ResolvedSwap swap = swaption.getUnderlying();
        LocalDate expiryDate = swaption.getExpiryDate();
        if (expiryDate.isBefore(ratesProvider.getValuationDate())) { // Option has expired already
            return CurrencyAmount.of(swap.getLegs().get(0).getCurrency(), 0d);
        }
        ResolvedSwapLeg cashFlowEquiv = CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, ratesProvider);
        int nPayments = cashFlowEquiv.getPaymentEvents().size();
        double[] alpha = new double[nPayments];
        double[] discountedCashFlow = new double[nPayments];
        for (int loopcf = 0; loopcf < nPayments; loopcf++) {
            NotionalExchange payment = (NotionalExchange) cashFlowEquiv.getPaymentEvents().get(loopcf);
            LocalDate maturityDate = payment.getPaymentDate();
            alpha[loopcf] = hwProvider.alpha(ratesProvider.getValuationDate(), expiryDate, expiryDate,
                    maturityDate);
            discountedCashFlow[loopcf] = paymentPricer.presentValueAmount(payment.getPayment(), ratesProvider);
        }
        double omega = (swap.getLegs(SwapLegType.FIXED).get(0).getPayReceive().isPay() ? -1d : 1d);
        double kappa = computeKappa(hwProvider, discountedCashFlow, alpha, omega);
        double pv = 0.0;
        for (int loopcf = 0; loopcf < nPayments; loopcf++) {
            pv += discountedCashFlow[loopcf] * NORMAL.getCDF(omega * (kappa + alpha[loopcf]));
        }
        return CurrencyAmount.of(cashFlowEquiv.getCurrency(), pv * (swaption.getLongShort().isLong() ? 1d : -1d));
    }

    //-------------------------------------------------------------------------
    /**
     * Calculates the currency exposure of the swaption product.
     * 
     * @param swaption  the product
     * @param ratesProvider  the rates provider
     * @param hwProvider  the Hull-White model parameter provider
     * @return the currency exposure
     */
    public MultiCurrencyAmount currencyExposure(ResolvedSwaption swaption, RatesProvider ratesProvider,
            HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {

        return MultiCurrencyAmount.of(presentValue(swaption, ratesProvider, hwProvider));
    }

    //-------------------------------------------------------------------------
    /**
     * Calculates the present value sensitivity of the swaption product.
     * <p>
     * The present value sensitivity of the product is the sensitivity of the present value to
     * the underlying curves.
     * 
     * @param swaption  the product
     * @param ratesProvider  the rates provider
     * @param hwProvider  the Hull-White model parameter provider
     * @return the point sensitivity to the rate curves
     */
    public PointSensitivityBuilder presentValueSensitivityRates(ResolvedSwaption swaption,
            RatesProvider ratesProvider, HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {

        validate(swaption, ratesProvider, hwProvider);
        ResolvedSwap swap = swaption.getUnderlying();
        LocalDate expiryDate = swaption.getExpiryDate();
        if (expiryDate.isBefore(ratesProvider.getValuationDate())) { // Option has expired already
            return PointSensitivityBuilder.none();
        }
        ImmutableMap<Payment, PointSensitivityBuilder> cashFlowEquivSensi = CashFlowEquivalentCalculator
                .cashFlowEquivalentAndSensitivitySwap(swap, ratesProvider);
        ImmutableList<Payment> list = cashFlowEquivSensi.keySet().asList();
        ImmutableList<PointSensitivityBuilder> listSensi = cashFlowEquivSensi.values().asList();
        int nPayments = list.size();
        double[] alpha = new double[nPayments];
        double[] discountedCashFlow = new double[nPayments];
        for (int loopcf = 0; loopcf < nPayments; loopcf++) {
            Payment payment = list.get(loopcf);
            alpha[loopcf] = hwProvider.alpha(ratesProvider.getValuationDate(), expiryDate, expiryDate,
                    payment.getDate());
            discountedCashFlow[loopcf] = paymentPricer.presentValueAmount(payment, ratesProvider);
        }
        double omega = (swap.getLegs(SwapLegType.FIXED).get(0).getPayReceive().isPay() ? -1d : 1d);
        double kappa = computeKappa(hwProvider, discountedCashFlow, alpha, omega);
        PointSensitivityBuilder point = PointSensitivityBuilder.none();
        for (int loopcf = 0; loopcf < nPayments; loopcf++) {
            Payment payment = list.get(loopcf);
            double cdf = NORMAL.getCDF(omega * (kappa + alpha[loopcf]));
            point = point
                    .combinedWith(paymentPricer.presentValueSensitivity(payment, ratesProvider).multipliedBy(cdf));
            if (!listSensi.get(loopcf).equals(PointSensitivityBuilder.none())) {
                point = point.combinedWith(listSensi.get(loopcf).multipliedBy(
                        cdf * ratesProvider.discountFactor(payment.getCurrency(), payment.getDate())));
            }
        }
        return swaption.getLongShort().isLong() ? point : point.multipliedBy(-1d);
    }

    //-------------------------------------------------------------------------
    /**
     * Calculates the present value sensitivity to piecewise constant volatility parameters of the Hull-White model.
     * 
     * @param swaption  the product
     * @param ratesProvider  the rates provider
     * @param hwProvider  the Hull-White model parameter provider
     * @return the present value Hull-White model parameter sensitivity of the swaption product
     */
    public DoubleArray presentValueSensitivityModelParamsHullWhite(ResolvedSwaption swaption,
            RatesProvider ratesProvider, HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {

        validate(swaption, ratesProvider, hwProvider);
        ResolvedSwap swap = swaption.getUnderlying();
        LocalDate expiryDate = swaption.getExpiryDate();
        if (expiryDate.isBefore(ratesProvider.getValuationDate())) { // Option has expired already
            return DoubleArray.EMPTY;
        }
        ResolvedSwapLeg cashFlowEquiv = CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, ratesProvider);
        int nPayments = cashFlowEquiv.getPaymentEvents().size();
        double[] alpha = new double[nPayments];
        double[][] alphaAdjoint = new double[nPayments][];
        double[] discountedCashFlow = new double[nPayments];
        for (int loopcf = 0; loopcf < nPayments; loopcf++) {
            NotionalExchange payment = (NotionalExchange) cashFlowEquiv.getPaymentEvents().get(loopcf);
            ValueDerivatives valueDeriv = hwProvider.alphaAdjoint(ratesProvider.getValuationDate(), expiryDate,
                    expiryDate, payment.getPaymentDate());
            alpha[loopcf] = valueDeriv.getValue();
            alphaAdjoint[loopcf] = valueDeriv.getDerivatives().toArray();
            discountedCashFlow[loopcf] = paymentPricer.presentValueAmount(payment.getPayment(), ratesProvider);
        }
        double omega = (swap.getLegs(SwapLegType.FIXED).get(0).getPayReceive().isPay() ? -1d : 1d);
        double kappa = computeKappa(hwProvider, discountedCashFlow, alpha, omega);
        int nParams = alphaAdjoint[0].length;
        if (Math.abs(kappa) > 1d / SMALL) { // decays exponentially
            return DoubleArray.filled(nParams);
        }
        double[] pvSensi = new double[nParams];
        double sign = (swaption.getLongShort().isLong() ? 1d : -1d);
        for (int i = 0; i < nParams; ++i) {
            for (int loopcf = 0; loopcf < nPayments; loopcf++) {
                pvSensi[i] += sign * discountedCashFlow[loopcf] * NORMAL.getPDF(omega * (kappa + alpha[loopcf]))
                        * omega * alphaAdjoint[loopcf][i];
            }
        }
        return DoubleArray.ofUnsafe(pvSensi);
    }

    //-------------------------------------------------------------------------
    // validate that the rates and volatilities providers are coherent
    private void validate(ResolvedSwaption swaption, RatesProvider ratesProvider,
            HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {
        ArgChecker.isTrue(hwProvider.getValuationDateTime().toLocalDate().equals(ratesProvider.getValuationDate()),
                "Hull-White model data and rate data should be for the same date");
        ArgChecker.isFalse(swaption.getUnderlying().isCrossCurrency(), "underlying swap should be single currency");
        ArgChecker.isTrue(swaption.getSwaptionSettlement().getSettlementType().equals(SettlementType.PHYSICAL),
                "swaption should be physical settlement");
    }

    // handling short time to expiry
    private double computeKappa(HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider,
            double[] discountedCashFlow, double[] alpha, double omega) {
        double kappa = 0d;
        if (DoubleArrayMath.fuzzyEqualsZero(alpha, SMALL)) { // threshold coherent to rootfinder in kappa computation
            double totalPv = DoubleArrayMath.sum(discountedCashFlow);
            kappa = totalPv * omega > 0d ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
        } else {
            kappa = hwProvider.getModel().kappa(DoubleArray.ofUnsafe(discountedCashFlow),
                    DoubleArray.ofUnsafe(alpha));
        }
        return kappa;
    }

}