/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.credit.index; import static com.opengamma.analytics.financial.credit.isdastandardmodel.DoublesScheduleGenerator.combineSets; import java.util.Arrays; import com.opengamma.analytics.financial.credit.isdastandardmodel.CDSAnalytic; import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantCreditCurve; import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantYieldCurve; import com.opengamma.analytics.math.MathException; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.rootfinding.NewtonRaphsonSingleRootFinder; import com.opengamma.util.ArgumentChecker; /** * */ public class PortfolioSwapAdjustment { private static final NewtonRaphsonSingleRootFinder ROOTFINDER = new NewtonRaphsonSingleRootFinder(); private final CDSIndexCalculator _pricer; /** * Default constructor */ public PortfolioSwapAdjustment() { _pricer = new CDSIndexCalculator(); } /** * Adjust the hazard rates of the credit curves of the individual single names in a index so that the index is priced exactly. The hazard rates are adjusted on * a percentage rather than a absolute bases (e.g. all hazard rates are increased by 1%) * @param indexPUF The clean price of the index for unit current notional (i.e. divide the actual clean price by the current notional) * @param indexCDS analytic description of a CDS traded at a certain time * @param indexCoupon The coupon of the index (as a fraction) * @param yieldCurve The yield curve * @param intrinsicData The credit curves of the individual single names making up the index * @return credit curve adjusted so they will exactly reprice the index. */ public IntrinsicIndexDataBundle adjustCurves(final double indexPUF, final CDSAnalytic indexCDS, final double indexCoupon, final ISDACompliantYieldCurve yieldCurve, final IntrinsicIndexDataBundle intrinsicData) { ArgumentChecker.isTrue(indexPUF <= 1.0, "indexPUF must be given as a fraction. Value of {} is too high.", indexPUF); ArgumentChecker.notNull(indexCDS, "indexCDS"); ArgumentChecker.isTrue(indexCoupon >= 0, "indexCoupon cannot be negative"); ArgumentChecker.isTrue(indexCoupon < 10, "indexCoupon should be a fraction. The value of {} would be a coupon of {}", indexCoupon, indexCoupon * 1e4); ArgumentChecker.notNull(yieldCurve, "yieldCurve"); ArgumentChecker.notNull(intrinsicData, "intrinsicData"); final Function1D<Double, Double> func = getHazardRateAdjFunction(indexPUF, indexCDS, indexCoupon, yieldCurve, intrinsicData); final double x = ROOTFINDER.getRoot(func, 1.0); final ISDACompliantCreditCurve[] adjCC = adjustCurves(intrinsicData.getCreditCurves(), x); return intrinsicData.withCreditCurves(adjCC); } /** * Adjust the hazard rates of the credit curves of the individual single names in a index so that the index is priced exactly at multiple terms. The * hazard rates are multiplied by a piecewise constant adjuster (e.g. all hazard rates between two index terms are increased by the same percentage). * When required extra knots are added to the credit curves, so the adjusted curves returned may contain more knots than the original curves. * @param indexPUF The clean prices of the index for unit current notional. * @param indexCDS analytic descriptions of the index for different terms * @param indexCoupon The coupon of the index (as a fraction) * @param yieldCurve The yield curve * @param intrinsicData The credit curves of the individual single names making up the index * @return credit curve adjusted so they will exactly reprice the index at the different terms. */ public IntrinsicIndexDataBundle adjustCurves(final double[] indexPUF, final CDSAnalytic[] indexCDS, final double indexCoupon, final ISDACompliantYieldCurve yieldCurve, final IntrinsicIndexDataBundle intrinsicData) { ArgumentChecker.notEmpty(indexPUF, "indexPUF"); ArgumentChecker.noNulls(indexCDS, "indexCDS"); final int nIndexTerms = indexCDS.length; ArgumentChecker.isTrue(nIndexTerms == indexPUF.length, "number of indexCDS ({}) does not match number of indexPUF ({})", nIndexTerms, indexPUF.length); if (nIndexTerms == 1) { return adjustCurves(indexPUF[0], indexCDS[0], indexCoupon, yieldCurve, intrinsicData); } ArgumentChecker.notNull(yieldCurve, "yieldCurve"); ArgumentChecker.notNull(intrinsicData, "intrinsicData"); ArgumentChecker.isTrue(indexCoupon >= 0, "indexCoupon cannot be negative"); ArgumentChecker.isTrue(indexCoupon < 10, "indexCoupon should be a fraction. The value of {} would be a coupon of {}", indexCoupon, indexCoupon * 1e4); final double[] indexKnots = new double[nIndexTerms]; for (int i = 0; i < nIndexTerms; i++) { ArgumentChecker.isTrue(indexPUF[i] <= 1.0, "indexPUF must be given as a fraction. Value of {} is too high.", indexPUF[i]); indexKnots[i] = indexCDS[i].getProtectionEnd(); if (i > 0) { ArgumentChecker.isTrue(indexKnots[i] > indexKnots[i - 1], "indexCDS must be in assending order of maturity"); } } final ISDACompliantCreditCurve[] creditCurves = intrinsicData.getCreditCurves(); final int nCurves = creditCurves.length; //we cannot assume that all the credit curves have knots at the same times or that the terms of the indices fall on these knots. ISDACompliantCreditCurve[] modCreditCurves = new ISDACompliantCreditCurve[nCurves]; final int[][] indexMap = new int[nCurves][nIndexTerms]; for (int i = 0; i < nCurves; i++) { if (creditCurves[i] == null) { modCreditCurves[i] = null; //null credit curves correspond to defaulted names, so are ignored } else { final double[] ccKnots = creditCurves[i].getKnotTimes(); final double[] comKnots = combineSets(ccKnots, indexKnots); final int nKnots = comKnots.length; if (nKnots == ccKnots.length) { modCreditCurves[i] = creditCurves[i]; } else { final double[] rt = new double[nKnots]; for (int j = 0; j < nKnots; j++) { rt[j] = creditCurves[i].getRT(comKnots[j]); } modCreditCurves[i] = ISDACompliantCreditCurve.makeFromRT(comKnots, rt); } for (int j = 0; j < nIndexTerms; j++) { final int index = Arrays.binarySearch(modCreditCurves[i].getKnotTimes(), indexKnots[j]); if (index < 0) { throw new MathException("This should not happen. There is a bug in the logic"); } indexMap[i][j] = index; } } } int[] startKnots = new int[nCurves]; final int[] endKnots = new int[nCurves]; double alpha = 1.0; for (int i = 0; i < nIndexTerms; i++) { if (i == (nIndexTerms - 1)) { for (int jj = 0; jj < nCurves; jj++) { if (modCreditCurves[jj] != null) { endKnots[jj] = modCreditCurves[jj].getNumberOfKnots(); } } } else { for (int jj = 0; jj < nCurves; jj++) { if (modCreditCurves[jj] != null) { endKnots[jj] = indexMap[jj][i] + 1; } } } final IntrinsicIndexDataBundle modIntrinsicData = intrinsicData.withCreditCurves(modCreditCurves); final Function1D<Double, Double> func = getHazardRateAdjFunction(indexPUF[i], indexCDS[i], indexCoupon, yieldCurve, modIntrinsicData, startKnots, endKnots); alpha = ROOTFINDER.getRoot(func, alpha); modCreditCurves = adjustCurves(modCreditCurves, alpha, startKnots, endKnots); startKnots = endKnots.clone(); } return intrinsicData.withCreditCurves(modCreditCurves); } private Function1D<Double, Double> getHazardRateAdjFunction(final double indexPUF, final CDSAnalytic indexCDS, final double indexCoupon, final ISDACompliantYieldCurve yieldCurve, final IntrinsicIndexDataBundle intrinsicData) { final ISDACompliantCreditCurve[] creditCurves = intrinsicData.getCreditCurves(); final double clean = intrinsicData.getIndexFactor() * indexPUF; return new Function1D<Double, Double>() { @Override public Double evaluate(final Double x) { final ISDACompliantCreditCurve[] adjCurves = adjustCurves(creditCurves, x); return _pricer.indexPV(indexCDS, indexCoupon, yieldCurve, intrinsicData.withCreditCurves(adjCurves)) - clean; } }; } private Function1D<Double, Double> getHazardRateAdjFunction(final double indexPUF, final CDSAnalytic indexCDS, final double indexCoupon, final ISDACompliantYieldCurve yieldCurve, final IntrinsicIndexDataBundle intrinsicData, final int[] firstKnots, final int[] lastKnots) { final ISDACompliantCreditCurve[] creditCurves = intrinsicData.getCreditCurves(); final double clean = intrinsicData.getIndexFactor() * indexPUF; return new Function1D<Double, Double>() { @Override public Double evaluate(final Double x) { final ISDACompliantCreditCurve[] adjCurves = adjustCurves(creditCurves, x, firstKnots, lastKnots); return _pricer.indexPV(indexCDS, indexCoupon, yieldCurve, intrinsicData.withCreditCurves(adjCurves)) - clean; } }; } private ISDACompliantCreditCurve[] adjustCurves(final ISDACompliantCreditCurve[] creditCurve, final double amount) { final int nCurves = creditCurve.length; final ISDACompliantCreditCurve[] adjCurves = new ISDACompliantCreditCurve[nCurves]; for (int jj = 0; jj < nCurves; jj++) { adjCurves[jj] = adjustCreditCurve(creditCurve[jj], amount); } return adjCurves; } private ISDACompliantCreditCurve adjustCreditCurve(final ISDACompliantCreditCurve creditCurve, final double amount) { if (creditCurve == null) { return creditCurve; } final int nKnots = creditCurve.getNumberOfKnots(); final double[] rt = creditCurve.getRt(); final double[] rtAdj = new double[nKnots]; for (int i = 0; i < nKnots; i++) { rtAdj[i] = rt[i] * amount; } return ISDACompliantCreditCurve.makeFromRT(creditCurve.getKnotTimes(), rtAdj); } private ISDACompliantCreditCurve[] adjustCurves(final ISDACompliantCreditCurve[] creditCurve, final double amount, final int[] firstKnots, final int[] lastknots) { final int nCurves = creditCurve.length; final ISDACompliantCreditCurve[] adjCurves = new ISDACompliantCreditCurve[nCurves]; for (int jj = 0; jj < nCurves; jj++) { if (creditCurve[jj] == null) { adjCurves[jj] = null; } else { adjCurves[jj] = adjustCreditCurve(creditCurve[jj], amount, firstKnots[jj], lastknots[jj]); } } return adjCurves; } private ISDACompliantCreditCurve adjustCreditCurve(final ISDACompliantCreditCurve creditCurve, final double amount, final int firstKnot, final int lastKnot) { final double[] rt = creditCurve.getRt(); final double[] rtAdj = rt.clone(); for (int i = firstKnot; i < lastKnot; i++) { rtAdj[i] = rt[i] * amount; } return ISDACompliantCreditCurve.makeFromRT(creditCurve.getKnotTimes(), rtAdj); } }