/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.provider.calculator.generic; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.Iterables; import com.opengamma.analytics.financial.interestrate.InstrumentDerivativeVisitor; import com.opengamma.analytics.financial.interestrate.annuity.derivative.Annuity; import com.opengamma.analytics.financial.interestrate.payments.derivative.Payment; import com.opengamma.analytics.financial.provider.description.interestrate.ParameterProviderInterface; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MulticurveSensitivity; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.rootfinding.BracketRoot; import com.opengamma.analytics.math.rootfinding.BrentSingleRootFinder; import com.opengamma.analytics.math.rootfinding.RealSingleRootFinder; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; import com.opengamma.util.money.MultipleCurrencyAmount; import com.opengamma.util.tuple.DoublesPair; /** * Calculates the z-spread of an annuity to a curve. The z-spread is defined as * the flat spread over the curve that is required to make the present value * equal to the price. * * @param <T> The type of {@link ParameterProviderInterface} that is required * to calculate present value for the annuity. */ public class ZSpreadCalculator<T extends ParameterProviderInterface> { /** Brackets a root */ private static final BracketRoot ROOT_BRACKETER = new BracketRoot(); /** The root-finder */ private static final RealSingleRootFinder ROOT_FINDER = new BrentSingleRootFinder(); /** The present value calculator */ private final InstrumentDerivativeVisitor<T, MultipleCurrencyAmount> _pvCalculator; /** The present value sensitivity calculator */ private final InstrumentDerivativeVisitor<T, MultipleCurrencyMulticurveSensitivity> _pvSensitivityCalculator; /** * @param pvCalculator The present value calculator, not null * @param pvSensitivityCalculator The present value sensitivity calculator, not null */ public ZSpreadCalculator(final InstrumentDerivativeVisitor<T, MultipleCurrencyAmount> pvCalculator, final InstrumentDerivativeVisitor<T, MultipleCurrencyMulticurveSensitivity> pvSensitivityCalculator) { ArgumentChecker.notNull(pvCalculator, "present value calculator"); ArgumentChecker.notNull(pvSensitivityCalculator, "curve sensitivity calculator"); _pvCalculator = pvCalculator; _pvSensitivityCalculator = pvSensitivityCalculator; } /** * Calculates the z-spread of an annuity given curves and a price. * @param annuity The annuity, not null * @param curves The curves, not null * @param price The price of the annuity * @return The z-spread */ public double calculateZSpread(final Annuity<? extends Payment> annuity, final T curves, final double price) { ArgumentChecker.notNull(annuity, "annuity"); ArgumentChecker.notNull(curves, "curves"); final Function1D<Double, Double> f = new Function1D<Double, Double>() { @Override public Double evaluate(final Double y) { return calculatePriceForZSpread(annuity, curves, y) - price; } }; final double[] range = ROOT_BRACKETER.getBracketedPoints(f, 0.0, 1.2); return ROOT_FINDER.getRoot(f, range[0], range[1]); } /** * Calculates the price of an annuity given curves and a z-spread. * @param annuity The annuity, not null * @param curves The curves, not null * @param zSpread The z-spread of the annuity * @return The price of the annuity */ public double calculatePriceForZSpread(final Annuity<? extends Payment> annuity, final T curves, final double zSpread) { ArgumentChecker.notNull(annuity, "annuity"); ArgumentChecker.notNull(curves, "curves"); double sum = 0; final int n = annuity.getNumberOfPayments(); Payment payment; for (int i = 0; i < n; i++) { payment = annuity.getNthPayment(i); final MultipleCurrencyAmount pvs = payment.accept(_pvCalculator, curves); if (pvs.size() != 1) { throw new IllegalStateException("Had more than one currency in result: " + pvs.getCurrencyAmounts()); } final double pv = Iterables.getOnlyElement(pvs).getAmount(); sum += pv * Math.exp(-zSpread * payment.getPaymentTime()); } return sum; } /** * Calculates the sensitivity of the price of an annuity to the z-spread. * @param annuity The annuity, not null * @param curves The curves, not null * @param zSpread The z-spread * @return The sensitivity of the price to the z-spread */ public double calculatePriceSensitivityToZSpread(final Annuity<? extends Payment> annuity, final T curves, final double zSpread) { ArgumentChecker.notNull(annuity, "annuity"); ArgumentChecker.notNull(curves, "curves"); double sum = 0; final int n = annuity.getNumberOfPayments(); Payment payment; for (int i = 0; i < n; i++) { payment = annuity.getNthPayment(i); final MultipleCurrencyAmount pvs = payment.accept(_pvCalculator, curves); if (pvs.size() != 1) { throw new IllegalStateException("Had more than one currency in result: " + pvs.getCurrencyAmounts()); } final double pv = Iterables.getOnlyElement(pvs).getAmount(); final double time = payment.getPaymentTime(); sum -= time * pv * Math.exp(-zSpread * time); } return sum; } /** * Calculates the sensitivity of the price of an annuity to the curve. * @param annuity The annuity, not null * @param curves The curves, not null * @param zSpread The z-spread * @return The sensitivity of the price to the z-spread */ public Map<String, List<DoublesPair>> calculatePriceSensitivityToCurve(final Annuity<? extends Payment> annuity, final T curves, final double zSpread) { ArgumentChecker.notNull(annuity, "annuity"); ArgumentChecker.notNull(curves, "curves"); final MultipleCurrencyMulticurveSensitivity pvss = annuity.accept(_pvSensitivityCalculator, curves); final Set<Currency> currencies = pvss.getCurrencies(); if (currencies.size() != 1) { throw new IllegalStateException("Had more than one currency in result: " + pvss.getCurrencies()); } final MulticurveSensitivity sensitivities = pvss.getSensitivity(Iterables.getOnlyElement(currencies)); if (Double.doubleToLongBits(zSpread) == 0) { return sensitivities.getYieldDiscountingSensitivities(); } final Map<String, List<DoublesPair>> result = new HashMap<>(); for (final Map.Entry<String, List<DoublesPair>> entry : sensitivities.getYieldDiscountingSensitivities().entrySet()) { final List<DoublesPair> unadjusted = entry.getValue(); final ArrayList<DoublesPair> adjusted = new ArrayList<>(unadjusted.size()); for (final DoublesPair pair : unadjusted) { final DoublesPair newPair = DoublesPair.of(pair.first, pair.second * Math.exp(-zSpread * pair.first)); adjusted.add(newPair); } result.put(entry.getKey(), adjusted); } return result; } /** * Calculates the sensitivity of the z-spread of an annuity to the curves. * @param annuity The annuity, not null * @param curves The curves, not null * @param zSpread The z-spread * @return The sensitivity of the price to the z-spread */ public Map<String, List<DoublesPair>> calculateZSpreadSensitivityToCurve(final Annuity<? extends Payment> annuity, final T curves, final double zSpread) { ArgumentChecker.notNull(annuity, "annuity"); ArgumentChecker.notNull(curves, "curves"); final double dPricedZ = calculatePriceSensitivityToZSpread(annuity, curves, zSpread); ArgumentChecker.isTrue(Double.doubleToLongBits(dPricedZ) != 0, "Price Sensitivity To ZSpread is zero"); final MultipleCurrencyMulticurveSensitivity pvss = annuity.accept(_pvSensitivityCalculator, curves); final Set<Currency> currencies = pvss.getCurrencies(); if (currencies.size() != 1) { throw new IllegalStateException("Had more than one currency in result: " + pvss.getCurrencies()); } final MulticurveSensitivity sensitivities = pvss.getSensitivity(Iterables.getOnlyElement(currencies)); if (Double.doubleToLongBits(zSpread) == 0) { return sensitivities.getYieldDiscountingSensitivities(); } final Map<String, List<DoublesPair>> result = new HashMap<>(); for (final Map.Entry<String, List<DoublesPair>> entry : sensitivities.getYieldDiscountingSensitivities().entrySet()) { final List<DoublesPair> unadjusted = entry.getValue(); final ArrayList<DoublesPair> adjusted = new ArrayList<>(unadjusted.size()); for (final DoublesPair pair : unadjusted) { final DoublesPair newPair = DoublesPair.of(pair.first, -pair.second * Math.exp(-zSpread * pair.first) / dPricedZ); adjusted.add(newPair); } result.put(entry.getKey(), adjusted); } return result; } }