/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.impl.credit.isda.e2e; import java.util.Arrays; import java.util.function.Function; import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.math.MathException; import com.opengamma.strata.math.impl.rootfinding.NewtonRaphsonSingleRootFinder; import com.opengamma.strata.pricer.impl.credit.isda.CdsAnalytic; import com.opengamma.strata.pricer.impl.credit.isda.DoublesScheduleGenerator; import com.opengamma.strata.pricer.impl.credit.isda.IsdaCompliantCreditCurve; import com.opengamma.strata.pricer.impl.credit.isda.IsdaCompliantYieldCurve; /** * */ public class PortfolioSwapAdjustment { // this code has been moved from src/main/java to src/test/java 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( double indexPUF, CdsAnalytic indexCDS, double indexCoupon, IsdaCompliantYieldCurve yieldCurve, IntrinsicIndexDataBundle intrinsicData) { ArgChecker.isTrue(indexPUF <= 1.0, "indexPUF must be given as a fraction. Value of {} is too high.", indexPUF); ArgChecker.notNull(indexCDS, "indexCDS"); ArgChecker.isTrue(indexCoupon >= 0, "indexCoupon cannot be negative"); ArgChecker.isTrue(indexCoupon < 10, "indexCoupon should be a fraction. The value of {} would be a coupon of {}", indexCoupon, indexCoupon * 1e4); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(intrinsicData, "intrinsicData"); Function<Double, Double> func = getHazardRateAdjFunction(indexPUF, indexCDS, indexCoupon, yieldCurve, intrinsicData); double x = ROOTFINDER.getRoot(func, 1.0); 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( double[] indexPUF, CdsAnalytic[] indexCDS, double indexCoupon, IsdaCompliantYieldCurve yieldCurve, IntrinsicIndexDataBundle intrinsicData) { ArgChecker.notEmpty(indexPUF, "indexPUF"); ArgChecker.noNulls(indexCDS, "indexCDS"); int nIndexTerms = indexCDS.length; ArgChecker.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); } ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(intrinsicData, "intrinsicData"); ArgChecker.isTrue(indexCoupon >= 0, "indexCoupon cannot be negative"); ArgChecker.isTrue(indexCoupon < 10, "indexCoupon should be a fraction. The value of {} would be a coupon of {}", indexCoupon, indexCoupon * 1e4); double[] indexKnots = new double[nIndexTerms]; for (int i = 0; i < nIndexTerms; i++) { ArgChecker.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) { ArgChecker.isTrue(indexKnots[i] > indexKnots[i - 1], "indexCDS must be in assending order of maturity"); } } IsdaCompliantCreditCurve[] creditCurves = intrinsicData.getCreditCurves(); 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]; 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 { double[] ccKnots = creditCurves[i].getKnotTimes(); double[] comKnots = DoublesScheduleGenerator.combineSets(ccKnots, indexKnots); int nKnots = comKnots.length; if (nKnots == ccKnots.length) { modCreditCurves[i] = creditCurves[i]; } else { 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++) { 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]; 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; } } } IntrinsicIndexDataBundle modIntrinsicData = intrinsicData.withCreditCurves(modCreditCurves); Function<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 Function<Double, Double> getHazardRateAdjFunction( double indexPUF, CdsAnalytic indexCDS, double indexCoupon, IsdaCompliantYieldCurve yieldCurve, IntrinsicIndexDataBundle intrinsicData) { IsdaCompliantCreditCurve[] creditCurves = intrinsicData.getCreditCurves(); double clean = intrinsicData.getIndexFactor() * indexPUF; return new Function<Double, Double>() { @Override public Double apply(Double x) { IsdaCompliantCreditCurve[] adjCurves = adjustCurves(creditCurves, x); return _pricer.indexPV(indexCDS, indexCoupon, yieldCurve, intrinsicData.withCreditCurves(adjCurves)) - clean; } }; } private Function<Double, Double> getHazardRateAdjFunction( double indexPUF, CdsAnalytic indexCDS, double indexCoupon, IsdaCompliantYieldCurve yieldCurve, IntrinsicIndexDataBundle intrinsicData, int[] firstKnots, int[] lastKnots) { IsdaCompliantCreditCurve[] creditCurves = intrinsicData.getCreditCurves(); double clean = intrinsicData.getIndexFactor() * indexPUF; return new Function<Double, Double>() { @Override public Double apply(Double x) { IsdaCompliantCreditCurve[] adjCurves = adjustCurves(creditCurves, x, firstKnots, lastKnots); return _pricer.indexPV(indexCDS, indexCoupon, yieldCurve, intrinsicData.withCreditCurves(adjCurves)) - clean; } }; } private IsdaCompliantCreditCurve[] adjustCurves(IsdaCompliantCreditCurve[] creditCurve, double amount) { int nCurves = creditCurve.length; IsdaCompliantCreditCurve[] adjCurves = new IsdaCompliantCreditCurve[nCurves]; for (int jj = 0; jj < nCurves; jj++) { adjCurves[jj] = adjustCreditCurve(creditCurve[jj], amount); } return adjCurves; } private IsdaCompliantCreditCurve adjustCreditCurve(IsdaCompliantCreditCurve creditCurve, double amount) { if (creditCurve == null) { return creditCurve; } int nKnots = creditCurve.getNumberOfKnots(); double[] rt = creditCurve.getRt(); 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( IsdaCompliantCreditCurve[] creditCurve, double amount, int[] firstKnots, int[] lastknots) { int nCurves = creditCurve.length; 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( IsdaCompliantCreditCurve creditCurve, double amount, int firstKnot, int lastKnot) { double[] rt = creditCurve.getRt(); double[] rtAdj = rt.clone(); for (int i = firstKnot; i < lastKnot; i++) { rtAdj[i] = rt[i] * amount; } return IsdaCompliantCreditCurve.makeFromRT(creditCurve.getKnotTimes(), rtAdj); } }