Java tutorial
/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of * companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate; import static com.opengamma.analytics.math.interpolation.Interpolator1DFactory.FLAT_EXTRAPOLATOR; import static com.opengamma.analytics.math.interpolation.Interpolator1DFactory.LINEAR_EXTRAPOLATOR; import static org.testng.AssertJUnit.assertEquals; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve; import com.opengamma.analytics.math.differentiation.VectorFieldFirstOrderDifferentiator; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolator; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory; import com.opengamma.analytics.math.interpolation.Interpolator1DFactory; import com.opengamma.analytics.math.matrix.DoubleMatrix1D; import com.opengamma.analytics.math.matrix.DoubleMatrix2D; import com.opengamma.analytics.math.rootfinding.YieldCurveFittingSetup; import com.opengamma.analytics.math.rootfinding.YieldCurveFittingTestDataBundle; import com.opengamma.analytics.math.rootfinding.YieldCurveFittingTestDataBundle.TestType; import com.opengamma.analytics.math.rootfinding.newton.BroydenVectorRootFinder; import com.opengamma.analytics.math.rootfinding.newton.NewtonVectorRootFinder; import com.opengamma.financial.convention.frequency.SimpleFrequency; import com.opengamma.util.tuple.DoublesPair; /** * */ public class InstrumentSingleCurveSensitivityCalculatorTest extends YieldCurveFittingSetup { private static final String CURVE_NAME = "single curve"; private static final Logger LOGGER = LoggerFactory .getLogger(InstrumentSingleCurveSensitivityCalculatorTest.class); private static final int WARMUP_CYCLES = 0; private static final int BENCHMARK_CYCLES = 1; private static final SimpleFrequency FRQ = SimpleFrequency.QUARTERLY; private static final CombinedInterpolatorExtrapolator INTERPOLATOR = CombinedInterpolatorExtrapolatorFactory .getInterpolator(Interpolator1DFactory.DOUBLE_QUADRATIC, LINEAR_EXTRAPOLATOR, FLAT_EXTRAPOLATOR); private static final NewtonVectorRootFinder ROOT_FINDER = new BroydenVectorRootFinder(EPS, EPS, STEPS); private static final Map<String, double[]> MARKET_DATA = getSingleCurveMarketData(); private static final Map<String, double[]> MATURITIES = getSingleCurveMaturities(); private static final YieldCurveFittingTestDataBundle PV_DATA = getSingleCurveSetup( PresentValueCalculator.getInstance(), PresentValueCurveSensitivityCalculator.getInstance(), MATURITIES, MARKET_DATA, true); private static final YieldCurveFittingTestDataBundle PAR_RATE_DATA = getSingleCurveSetup( ParRateCalculator.getInstance(), ParRateCurveSensitivityCalculator.getInstance(), MATURITIES, MARKET_DATA, false); private static final Function1D<DoubleMatrix1D, DoubleMatrix1D> PV_FUNCTION = new MultipleYieldCurveFinderFunction( PV_DATA, PV_DATA.getMarketValueCalculator()); private static final Function1D<DoubleMatrix1D, DoubleMatrix2D> PV_JACOBIAN_FUNCTION; private static final Function1D<DoubleMatrix1D, DoubleMatrix1D> PAR_RATE_FUNCTION = new MultipleYieldCurveFinderFunction( PAR_RATE_DATA, PAR_RATE_DATA.getMarketValueCalculator()); private static final Function1D<DoubleMatrix1D, DoubleMatrix2D> PAR_RATE_JACOBIAN_FUNCTION; private static final DoubleMatrix1D PV_YIELD_CURVE_NODES; private static final DoubleMatrix2D PV_JACOBIAN; private static final DoubleMatrix1D PAR_RATE_YIELD_CURVE_NODES; private static final DoubleMatrix2D PAR_RATE_JACOBIAN; private static final DoubleMatrix1D PV_COUPON_SENSITIVITY; private static final YieldCurveBundle PV_CURVES; private static final YieldCurveBundle PV_ALL_CURVES; private static final YieldCurveBundle PAR_RATE_CURVES; private static final YieldCurveBundle PAR_RATE_ALL_CURVES; private static final InstrumentSensitivityCalculator ISC = InstrumentSensitivityCalculator.getInstance(); private static final PresentValueCouponSensitivityCalculator PVCS = PresentValueCouponSensitivityCalculator .getInstance(); private static final PresentValueNodeSensitivityCalculator PVNS = PresentValueNodeSensitivityCalculator .getDefaultInstance(); static { if (PV_DATA.getTestType() == TestType.ANALYTIC_JACOBIAN) { PV_JACOBIAN_FUNCTION = new MultipleYieldCurveFinderJacobian(PV_DATA, PV_DATA.getMarketValueSensitivityCalculator()); } else if (PV_DATA.getTestType() == TestType.FD_JACOBIAN) { final VectorFieldFirstOrderDifferentiator fdJacCalculator = new VectorFieldFirstOrderDifferentiator(); PV_JACOBIAN_FUNCTION = fdJacCalculator.differentiate(PV_FUNCTION); } else { throw new IllegalArgumentException("unknown TestType " + PV_DATA.getTestType()); } if (PAR_RATE_DATA.getTestType() == TestType.ANALYTIC_JACOBIAN) { PAR_RATE_JACOBIAN_FUNCTION = new MultipleYieldCurveFinderJacobian(PAR_RATE_DATA, PAR_RATE_DATA.getMarketValueSensitivityCalculator()); } else if (PAR_RATE_DATA.getTestType() == TestType.FD_JACOBIAN) { final VectorFieldFirstOrderDifferentiator fdJacCalculator = new VectorFieldFirstOrderDifferentiator(); PAR_RATE_JACOBIAN_FUNCTION = fdJacCalculator.differentiate(PAR_RATE_FUNCTION); } else { throw new IllegalArgumentException("unknown TestType " + PAR_RATE_DATA.getTestType()); } PV_YIELD_CURVE_NODES = ROOT_FINDER.getRoot(PV_FUNCTION, PV_JACOBIAN_FUNCTION, PV_DATA.getStartPosition()); PV_JACOBIAN = PV_JACOBIAN_FUNCTION.evaluate(PV_YIELD_CURVE_NODES); PAR_RATE_YIELD_CURVE_NODES = ROOT_FINDER.getRoot(PAR_RATE_FUNCTION, PAR_RATE_JACOBIAN_FUNCTION, PAR_RATE_DATA.getStartPosition()); PAR_RATE_JACOBIAN = PAR_RATE_JACOBIAN_FUNCTION.evaluate(PAR_RATE_YIELD_CURVE_NODES); PAR_RATE_CURVES = getYieldCurveMap(PAR_RATE_DATA, PAR_RATE_YIELD_CURVE_NODES); PAR_RATE_ALL_CURVES = getAllCurves(PAR_RATE_DATA, PAR_RATE_CURVES); PV_CURVES = getYieldCurveMap(PV_DATA, PV_YIELD_CURVE_NODES); PV_ALL_CURVES = getAllCurves(PV_DATA, PV_CURVES); final double[] couponSensitivityArray = new double[PV_DATA.getNumInstruments()]; for (int i = 0; i < PV_DATA.getNumInstruments(); i++) { couponSensitivityArray[i] = PVCS.visit(PV_DATA.getDerivative(i), PV_ALL_CURVES); } PV_COUPON_SENSITIVITY = new DoubleMatrix1D(couponSensitivityArray); } @Override protected Logger getLogger() { return LOGGER; } @Override protected int getWarmupCycles() { return WARMUP_CYCLES; } @Override protected int getBenchmarkCycles() { return BENCHMARK_CYCLES; } @Test public void testWithParRate() { for (int i = 0; i < PAR_RATE_DATA.getNumInstruments(); i++) { final DoubleMatrix1D bucketedDelta = ISC.calculateFromParRate(PAR_RATE_DATA.getDerivative(i), PAR_RATE_DATA.getKnownCurves(), PAR_RATE_CURVES, PAR_RATE_JACOBIAN, PVNS); final double sensitivity = PVCS.visit(PAR_RATE_DATA.getDerivative(i), PAR_RATE_ALL_CURVES); assertEquals(-sensitivity, bucketedDelta.getEntry(i), 1e-8); for (int j = 0; j < PAR_RATE_DATA.getNumInstruments(); j++) { if (j != i) { assertEquals(0.0, bucketedDelta.getEntry(j), 1e-8); } } } } @Test public void testwithPV() { // PresentValueCouponSensitivityCalculator is sensitivity to change in the // coupon rate for that instrument - what we calculate here is the // (hypothetical) change of PV of the instrument with a fixed coupon when // its par-rate changes - this is exactly the negative of the coupon // sensitivity for (int i = 0; i < PV_DATA.getNumInstruments(); i++) { final DoubleMatrix1D bucketedDelta = ISC.calculateFromPresentValue(PV_DATA.getDerivative(i), PV_DATA.getKnownCurves(), PV_CURVES, PV_COUPON_SENSITIVITY, PV_JACOBIAN, PVNS); assertEquals(-PV_COUPON_SENSITIVITY.getEntry(i), bucketedDelta.getEntry(i), 1e-8); for (int j = 0; j < PV_DATA.getNumInstruments(); j++) { if (j != i) { assertEquals(0.0, bucketedDelta.getEntry(j), 1e-8); } } } } @Test public void testBumpedData() { final double notional = 10394850; final double eps = 1e-3; final InstrumentDerivative libor = makeSingleCurrencyIRD("libor", 1.5, FRQ, CURVE_NAME, CURVE_NAME, 0.04, notional); testBumpedDataParRateMethod(libor, eps); testBumpedDataPVMethod(libor, eps); InstrumentDerivative swap = makeSingleCurrencyIRD("swap", 13, FRQ, CURVE_NAME, CURVE_NAME, 0.048, notional); testBumpedDataParRateMethod(swap, eps); testBumpedDataPVMethod(swap, eps); final InstrumentDerivative fra = makeSingleCurrencyIRD("fra", 0.6666, FRQ, CURVE_NAME, CURVE_NAME, 0.02, notional); testBumpedDataParRateMethod(fra, eps); testBumpedDataPVMethod(fra, eps); final InstrumentDerivative future = makeSingleCurrencyIRD("fra", 2, FRQ, CURVE_NAME, CURVE_NAME, 0.03, notional); testBumpedDataParRateMethod(future, eps); testBumpedDataPVMethod(future, eps); swap = makeSingleCurrencyIRD("swap", 19, FRQ, CURVE_NAME, CURVE_NAME, 0.05, notional); testBumpedDataParRateMethod(swap, eps); testBumpedDataPVMethod(swap, eps); } private void testBumpedDataParRateMethod(final InstrumentDerivative ird, final double eps) { final DoubleMatrix1D sensitivities = ISC.calculateFromParRate(ird, null, PAR_RATE_CURVES, PAR_RATE_JACOBIAN, PVNS); final PresentValueCalculator calculator = PresentValueCalculator.getInstance(); final double pv1 = calculator.visit(ird, PAR_RATE_ALL_CURVES); for (int i = 0; i < sensitivities.getNumberOfElements(); i++) { final YieldCurveFittingTestDataBundle bumpedData = getSingleCurveSetup(ParRateCalculator.getInstance(), ParRateCurveSensitivityCalculator.getInstance(), MATURITIES, getBumpedData(i, eps), false); final Function1D<DoubleMatrix1D, DoubleMatrix1D> f = new MultipleYieldCurveFinderFunction(bumpedData, bumpedData.getMarketValueCalculator()); final Function1D<DoubleMatrix1D, DoubleMatrix2D> jf = new MultipleYieldCurveFinderJacobian(bumpedData, bumpedData.getMarketValueSensitivityCalculator()); final DoubleMatrix1D bumpedNodes = ROOT_FINDER.getRoot(f, jf, bumpedData.getStartPosition()); final YieldCurveBundle bumpedCurves = getYieldCurveMap(bumpedData, bumpedNodes); final YieldCurveBundle allBumpedCurves = getAllCurves(bumpedData, bumpedCurves); final double pv2 = calculator.visit(ird, allBumpedCurves); final double delta = pv2 - pv1; if (Math.abs(sensitivities.getEntry(i)) > 1e-3) { assertEquals(0, (delta - sensitivities.getEntry(i) * eps) / sensitivities.getEntry(i), eps); } else { assertEquals(0, sensitivities.getEntry(i), 1e-4); assertEquals(0, delta, 1e-4); } } } private void testBumpedDataPVMethod(final InstrumentDerivative ird, final double eps) { final DoubleMatrix1D sensitivities = ISC.calculateFromPresentValue(ird, null, PV_CURVES, PV_COUPON_SENSITIVITY, PV_JACOBIAN, PVNS); final PresentValueCalculator calculator = PresentValueCalculator.getInstance(); final double pv1 = calculator.visit(ird, PV_ALL_CURVES); for (int i = 0; i < sensitivities.getNumberOfElements(); i++) { final YieldCurveFittingTestDataBundle bumpedData = getSingleCurveSetup( PresentValueCalculator.getInstance(), PresentValueCurveSensitivityCalculator.getInstance(), MATURITIES, getBumpedData(i, eps), true); final Function1D<DoubleMatrix1D, DoubleMatrix1D> f = new MultipleYieldCurveFinderFunction(bumpedData, bumpedData.getMarketValueCalculator()); final Function1D<DoubleMatrix1D, DoubleMatrix2D> jf = new MultipleYieldCurveFinderJacobian(bumpedData, bumpedData.getMarketValueSensitivityCalculator()); final DoubleMatrix1D bumpedNodes = ROOT_FINDER.getRoot(f, jf, bumpedData.getStartPosition()); final YieldCurveBundle bumpedCurves = getYieldCurveMap(bumpedData, bumpedNodes); final YieldCurveBundle allBumpedCurves = getAllCurves(bumpedData, bumpedCurves); final double pv2 = calculator.visit(ird, allBumpedCurves); final double delta = pv2 - pv1; if (Math.abs(sensitivities.getEntry(i)) > 1e-3) { assertEquals(0, (delta - sensitivities.getEntry(i) * eps) / sensitivities.getEntry(i), eps); } else { assertEquals(0, sensitivities.getEntry(i), 1e-4); assertEquals(0, delta, 1e-4); } } } private static YieldCurveBundle getAllCurves(final YieldCurveFittingTestDataBundle data, final YieldCurveBundle curves) { final YieldCurveBundle allCurves = new YieldCurveBundle(curves); if (data.getKnownCurves() != null) { allCurves.addAll(data.getKnownCurves()); } return allCurves; } private static YieldCurveBundle getYieldCurveMap(final YieldCurveFittingTestDataBundle data, final DoubleMatrix1D yieldCurveNodes) { final HashMap<String, double[]> yields = unpackYieldVector(data, yieldCurveNodes); final LinkedHashMap<String, YieldAndDiscountCurve> curves = new LinkedHashMap<String, YieldAndDiscountCurve>(); for (final String name : data.getCurveNames()) { final YieldAndDiscountCurve curve = makeYieldCurve(yields.get(name), data.getCurveNodePointsForCurve(name), data.getInterpolatorForCurve(name)); curves.put(name, curve); } return new YieldCurveBundle(curves); } private static Map<String, double[]> getSingleCurveMaturities() { final LinkedHashMap<String, double[]> maturities = new LinkedHashMap<String, double[]>(); maturities.put("libor", new double[] { 0.25 }); maturities.put("fra", new double[] { 0.51, 0.74 }); maturities.put("swap", new double[] { 1, 2, 3, 4, 5, 6, 7, 10, 15, 20, 25, 30 }); return maturities; } private static Map<String, double[]> getSingleCurveMarketData() { final LinkedHashMap<String, double[]> marketRates = new LinkedHashMap<String, double[]>(); marketRates.put("libor", new double[] { 0.02 }); // marketRates.put("fra", new double[] { 0.0366, 0.04705 }); marketRates.put("swap", new double[] { 0.04285, 0.03953, 0.03986, 0.040965, 0.042035, 0.04314, 0.044, 0.046045, 0.048085, 0.048925, 0.049155, 0.049195 }); return marketRates; } private static Map<String, double[]> getBumpedData(final int n, final double eps) { final Map<String, double[]> data = new LinkedHashMap<String, double[]>(getSingleCurveMarketData()); int i = 0; for (final Map.Entry<String, double[]> entry : data.entrySet()) { final double[] value = entry.getValue(); for (int j = 0; j < value.length; j++) { if (i == n) { value[j] += eps; } i++; } } return data; } private static YieldCurveFittingTestDataBundle getSingleCurveSetup( final InstrumentDerivativeVisitor<YieldCurveBundle, Double> calculator, final InstrumentDerivativeVisitor<YieldCurveBundle, Map<String, List<DoublesPair>>> sensitivityCalculator, final Map<String, double[]> maturities, final Map<String, double[]> marketRates, final boolean isPV) { final List<String> curveNames = new ArrayList<String>(); curveNames.add(CURVE_NAME); int nNodes = 0; for (final double[] temp : maturities.values()) { nNodes += temp.length; } final double[] temp = new double[nNodes]; int index = 0; for (final double[] times : maturities.values()) { for (final double t : times) { temp[index++] = t; } } Arrays.sort(temp); final List<double[]> curveKnots = new ArrayList<double[]>(); curveKnots.add(temp); // now get market prices final double[] marketValues = new double[nNodes]; final List<InstrumentDerivative> instruments = new ArrayList<InstrumentDerivative>(); InstrumentDerivative ird; index = 0; for (final String name : maturities.keySet()) { final double[] times = maturities.get(name); final double[] rates = marketRates.get(name); Validate.isTrue(times.length == rates.length); for (int i = 0; i < times.length; i++) { ird = makeSingleCurrencyIRD(name, times[i], FRQ, CURVE_NAME, CURVE_NAME, rates[i], 1); instruments.add(ird); if (isPV) { marketValues[index] = 0; } else { marketValues[index] = rates[i]; } index++; } } final double[] rates = new double[nNodes]; for (int i = 0; i < nNodes; i++) { rates[i] = 0.04; } rates[0] = 0.02; final DoubleMatrix1D startPosition = new DoubleMatrix1D(rates); final YieldCurveFittingTestDataBundle data = getYieldCurveFittingTestDataBundle(instruments, null, curveNames, curveKnots, INTERPOLATOR, calculator, sensitivityCalculator, marketValues, startPosition, null, false, FX_MATRIX); return data; } }