/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.interestrate.curve; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.ArrayUtils; import com.opengamma.analytics.ShiftType; import com.opengamma.analytics.math.curve.AddCurveSpreadFunction; import com.opengamma.analytics.math.curve.ConstantDoublesCurve; import com.opengamma.analytics.math.curve.CurveShiftFunctionFactory; import com.opengamma.analytics.math.curve.CurveSpreadFunction; import com.opengamma.analytics.math.curve.DoublesCurve; import com.opengamma.analytics.math.curve.InterpolatedDoublesCurve; import com.opengamma.analytics.math.curve.MultiplyCurveSpreadFunction; import com.opengamma.analytics.math.curve.SpreadDoublesCurve; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory; import com.opengamma.analytics.math.interpolation.Interpolator1D; import com.opengamma.analytics.math.interpolation.Interpolator1DFactory; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.DoublesPair; /** * Class containing utility methods for manipulating yield curves. */ public class YieldCurveUtils { /** Curve spread function that adds two curves */ private static final CurveSpreadFunction ADD_SPREAD = AddCurveSpreadFunction.getInstance(); /** Curve spread function that multiplies one curve by another */ private static final CurveSpreadFunction MULTIPLY_SPREAD = MultiplyCurveSpreadFunction.getInstance(); /** Suffix for parallel shifts */ public static final String PARALLEL_SHIFT_NAME = "_WithParallelShift"; /** Suffix for bucketed shifts */ public static final String BUCKETED_SHIFT_NAME = "_WithBucketedShifts"; /** Suffix for point shifts */ public static final String POINT_SHIFT_NAME = "_WithPointShifts"; /** * Shifts a curve by a constant amount over all tenors. If the {@link ShiftType} is * absolute, the shift is added to the curve i.e. a shift of 0.0001 results in all * yields on the curve having one basis point added. If it is relative, then all yields on * are the curve are multiplied by the shift amount i.e. a relative shift of 0.01 will * result in all points on the curve being shifted upwards by 1% of the yield. * <p> * The original curve is unchanged and a new curve is returned. * @param curve The original curve, not null * @param shift The shift * @param shiftType The shift type, not null * @return A new curve with all values shifted by a constant amount */ public static YieldCurve withParallelShift(YieldCurve curve, double shift, ShiftType shiftType) { return withParallelShift(curve, shift, shiftType, PARALLEL_SHIFT_NAME); } /** * Shifts a curve by a constant amount over all tenors. If the {@link ShiftType} is * absolute, the shift is added to the curve i.e. a shift of 0.0001 results in all * yields on the curve having one basis point added. If it is relative, then all yields on * are the curve are multiplied by the shift amount i.e. a relative shift of 0.01 will * result in all points on the curve being shifted upwards by 1% of the yield. * <p> * The original curve is unchanged and a new curve is returned. * @param curve The original curve, not null * @param shift The shift * @param shiftType The shift type, not null * @param nameSuffix Suffix to add to the name of the shifted curve * @return A new curve with all values shifted by a constant amount */ public static YieldCurve withParallelShift(YieldCurve curve, double shift, ShiftType shiftType, String nameSuffix) { ArgumentChecker.notNull(curve, "curve"); ArgumentChecker.notNull(shiftType, "shift type"); ArgumentChecker.notNull(nameSuffix, "nameSuffix"); String newName = curve.getName() + nameSuffix; DoublesCurve underlyingCurve = curve.getCurve(); switch (shiftType) { case ABSOLUTE: return new YieldCurve( newName, SpreadDoublesCurve.from(ADD_SPREAD, newName, underlyingCurve, ConstantDoublesCurve.from(shift))); case RELATIVE: return new YieldCurve( newName, SpreadDoublesCurve.from(MULTIPLY_SPREAD, newName, underlyingCurve, ConstantDoublesCurve.from(1 + shift))); default: throw new IllegalArgumentException("Cannot handle curve shift type " + shiftType + " for parallel shifts"); } } /** * Performs bucketed shifts on curves. The buckets need not be continuous; if they are not, * then the curve is unchanged between the two times. The shifts include the lower time but * exclude the upper, and are applied as a step function (i.e. constant over the bucket). * The units of time of the buckets are years. * <p> * If the {@link ShiftType} is absolute, the shift is added to the curve; a shift of 0.0001 * from one year to two years results in the curve being shifted upwards by one basis point * from the one year point to the two year point. If this shift is relative, the yields are * multiplied by the shift amount. * <p> * The original curve is unchanged and a new curve is returned. * @param curve The original curve, not null * @param buckets The buckets, not null * @param shifts The shifts, not null. There must be one shift per bucket. * @param shiftType The shift type, not null * @return A new curve with bucketed shifts applied */ public static YieldCurve withBucketedShifts(YieldCurve curve, List<DoublesPair> buckets, List<Double> shifts, ShiftType shiftType) { return withBucketedShifts(curve, buckets, shifts, shiftType, BUCKETED_SHIFT_NAME); } /** * Performs bucketed shifts on curves. The buckets need not be continuous; if they are not, * then the curve is unchanged between the two times. The shifts include the lower time but * exclude the upper, and are applied as a step function (i.e. constant over the bucket). * The units of time of the buckets are years. * <p> * If the {@link ShiftType} is absolute, the shift is added to the curve; a shift of 0.0001 * from one year to two years results in the curve being shifted upwards by one basis point * from the one year point to the two year point. If this shift is relative, the yields are * multiplied by the shift amount. * <p> * The original curve is unchanged and a new curve is returned. * @param curve The original curve, not null * @param buckets The buckets, not null * @param shifts The shifts, not null. There must be one shift per bucket. * @param shiftType The shift type, not null * @param nameSuffix Suffix to add to the name of the shifted curve * @return A new curve with bucketed shifts applied */ public static YieldCurve withBucketedShifts(YieldCurve curve, List<DoublesPair> buckets, List<Double> shifts, ShiftType shiftType, String nameSuffix) { ArgumentChecker.notNull(curve, "curve"); ArgumentChecker.noNulls(buckets, "buckets"); ArgumentChecker.noNulls(shifts, "shifts"); ArgumentChecker.isTrue(buckets.size() == shifts.size(), "must have one shift per bucket"); ArgumentChecker.notNull(shiftType, "shift type"); ArgumentChecker.notNull(nameSuffix, "nameSuffix"); String newName = curve.getName() + nameSuffix; DoublesCurve underlyingCurve = curve.getCurve(); if (buckets.isEmpty()) { return new YieldCurve( newName, SpreadDoublesCurve.from(ADD_SPREAD, newName, underlyingCurve, ConstantDoublesCurve.from(0))); } List<DoublesPair> stepCurvePoints = new ArrayList<>(); Iterator<DoublesPair> iterBuckets = buckets.iterator(); Iterator<Double> iterShifts = shifts.iterator(); DoublesPair oldPair = iterBuckets.next(); double shift = iterShifts.next(); Interpolator1D stepInterpolator = CombinedInterpolatorExtrapolatorFactory.getInterpolator( Interpolator1DFactory.STEP, Interpolator1DFactory.FLAT_EXTRAPOLATOR); switch (shiftType) { case ABSOLUTE: { if (oldPair.getFirstDouble() >= 0 && Double.compare(0, oldPair.getFirstDouble()) != 0) { stepCurvePoints.add(DoublesPair.of(0., 0.)); } stepCurvePoints.add(DoublesPair.of(oldPair.getFirstDouble(), shift)); while (iterBuckets.hasNext()) { DoublesPair pair = iterBuckets.next(); shift = iterShifts.next(); if (Double.compare(pair.getFirstDouble(), oldPair.getSecondDouble()) != 0) { stepCurvePoints.add(DoublesPair.of(oldPair.getSecondDouble(), 0)); stepCurvePoints.add(DoublesPair.of(pair.getFirstDouble(), shift)); } else { stepCurvePoints.add(DoublesPair.of(oldPair.getSecondDouble(), shift)); } oldPair = pair; } stepCurvePoints.add(DoublesPair.of(oldPair.getSecondDouble(), 0)); DoublesCurve spreadCurve = InterpolatedDoublesCurve.from(stepCurvePoints, stepInterpolator); return new YieldCurve(newName, SpreadDoublesCurve.from(ADD_SPREAD, newName, underlyingCurve, spreadCurve)); } case RELATIVE: { if (oldPair.getFirstDouble() >= 0 && Double.compare(0, oldPair.getFirstDouble()) != 0) { stepCurvePoints.add(DoublesPair.of(0., 1.)); } stepCurvePoints.add(DoublesPair.of(oldPair.getFirstDouble(), 1 + shift)); while (iterBuckets.hasNext()) { DoublesPair pair = iterBuckets.next(); shift = iterShifts.next(); if (Double.compare(pair.getFirstDouble(), oldPair.getSecondDouble()) != 0) { stepCurvePoints.add(DoublesPair.of(oldPair.getSecondDouble(), 1)); stepCurvePoints.add(DoublesPair.of(pair.getFirstDouble(), 1 + shift)); } else { stepCurvePoints.add(DoublesPair.of(oldPair.getSecondDouble(), 1 + shift)); } oldPair = pair; } stepCurvePoints.add(DoublesPair.of(oldPair.getSecondDouble(), 1)); DoublesCurve spreadCurve = InterpolatedDoublesCurve.from(stepCurvePoints, stepInterpolator); return new YieldCurve(newName, SpreadDoublesCurve.from(MULTIPLY_SPREAD, newName, underlyingCurve, spreadCurve)); } default: throw new IllegalArgumentException("Cannot handle curve shift type " + shiftType + " for bucketed shifts"); } } /** * Performs point shifts on curves. The units of time are years. * <p> * If the {@link ShiftType} is absolute, the shift is added to the curve; a shift of 0.0001 * results in the curve being shifted upwards by one basis point at the time point. If this * shift is relative, the yields are multiplied by the shift amount. * <p> * The original curve is unchanged and a new curve is returned. * <p> * This method only works for interpolated yield curves. * @param curve The original curve, not null * @param t The times, not null * @param shifts The shifts, not null. There must be one shift per time. * @param shiftType The shift type, not null * @return A new curve with point shifts applied * @throws IllegalArgumentException if the curve is not an interpolated curve */ public static YieldCurve withPointShifts(YieldCurve curve, List<Double> t, List<Double> shifts, ShiftType shiftType) { return withPointShifts(curve, t, shifts, shiftType, POINT_SHIFT_NAME); } /** * Performs point shifts on curves. The units of time are years. * <p> * If the {@link ShiftType} is absolute, the shift is added to the curve; a shift of 0.0001 * results in the curve being shifted upwards by one basis point at the time point. If this * shift is relative, the yields are multiplied by the shift amount. * <p> * The original curve is unchanged and a new curve is returned. * <p> * This method only works for interpolated yield curves. * @param curve The original curve, not null * @param t The times, not null * @param shifts The shifts, not null. There must be one shift per time. * @param shiftType The shift type, not null * @param nameSuffix Suffix to add to the name of the shifted curve * @return A new curve with point shifts applied * @throws IllegalArgumentException if the curve is not an interpolated curve */ public static YieldCurve withPointShifts(YieldCurve curve, List<Double> t, List<Double> shifts, ShiftType shiftType, String nameSuffix) { ArgumentChecker.notNull(curve, "curve"); ArgumentChecker.noNulls(t, "times"); ArgumentChecker.noNulls(shifts, "shifts"); ArgumentChecker.notNull(nameSuffix, "nameSuffix"); ArgumentChecker.isTrue(t.size() == shifts.size(), "must have one shift per point"); ArgumentChecker.isTrue( curve.getCurve() instanceof InterpolatedDoublesCurve, "Can only perform points shifts on interpolated curves"); ArgumentChecker.notNull(shiftType, "shift type"); String newName = curve.getName() + nameSuffix; int n = t.size(); double[] tArray = ArrayUtils.toPrimitive(t.toArray(new Double[n])); double[] shiftArray = ArrayUtils.toPrimitive(shifts.toArray(new Double[n])); switch (shiftType) { case ABSOLUTE: { return new YieldCurve( newName, CurveShiftFunctionFactory.getShiftedCurve(curve.getCurve(), tArray, shiftArray, newName)); } case RELATIVE: { InterpolatedDoublesCurve interpolatedCurve = (InterpolatedDoublesCurve) curve.getCurve(); return new YieldCurve(newName, getRelativeShiftedCurve(interpolatedCurve, tArray, shiftArray, newName)); } default: throw new IllegalArgumentException("Cannot handle curve shift type " + shiftType + " for point shifts"); } } /** * Performs relative shifts on a yield curve * @param curve The curve * @param t The times * @param yShift The shifts * @param newName The new curve name * @return A shifted curve */ //TODO this should be moved into a separate CurveShiftFunction private static InterpolatedDoublesCurve getRelativeShiftedCurve(InterpolatedDoublesCurve curve, double[] t, double[] yShift, String newName) { if (t.length == 0) { return InterpolatedDoublesCurve.from( curve.getXDataAsPrimitive(), curve.getYDataAsPrimitive(), curve.getInterpolator(), newName); } List<Double> newX = new ArrayList<>(Arrays.asList(curve.getXData())); List<Double> newY = new ArrayList<>(Arrays.asList(curve.getYData())); for (int i = 0; i < t.length; i++) { int index = newX.indexOf(t[i]); if (index >= 0) { newY.set(index, newY.get(index) * (1 + yShift[i])); } else { newX.add(t[i]); newY.add(curve.getYValue(t[i]) * (1 + yShift[i])); } } return InterpolatedDoublesCurve.from(newX, newY, curve.getInterpolator(), newName); } }