/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.credit.isdayieldcurve;
import static com.opengamma.analytics.math.interpolation.Interpolator1DFactory.FLAT_EXTRAPOLATOR;
import static com.opengamma.analytics.math.interpolation.Interpolator1DFactory.ISDA_EXTRAPOLATOR;
import static com.opengamma.analytics.math.interpolation.Interpolator1DFactory.ISDA_INTERPOLATOR;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.ObjectUtils;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.analytics.financial.interestrate.PeriodicInterestRate;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.curve.DoublesCurve;
import com.opengamma.analytics.math.curve.InterpolatedDoublesCurve;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolator;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.financial.convention.daycount.DayCount;
import com.opengamma.financial.convention.daycount.DayCountFactory;
import com.opengamma.util.ArgumentChecker;
/**
*The underlying curve is the zero default rate to time t, H(t), defined as H(t) = -ln[Q(0,t)]/t, where Q(0,t) is the survival probably from now (time 0)
*to time t. This currently extends YieldAndDiscountCurve and thus uses the nomenclature of the rates world. The links are
*<ul>
*<li>Discount factor P(0,t) <==> survival probability Q(0,t) </li>
*<li>Zero rate (or yield) R(t) = -ln[P(0,t)]/t <==> Zero default rate H(t) = -ln[Q(0,t)]/t</li>
*</ul>
*The underlying curve is a linear interpolation of the quantity t*H(t) = -ln[Q(0,t)], which is the ISDA model standard
*/
public class ISDADateCurve extends YieldAndDiscountCurve {
private static final DayCount ACT_365 = DayCountFactory.INSTANCE.getDayCount("ACT/365");
@SuppressWarnings("unused")
private static final DayCount ACT_360 = DayCountFactory.INSTANCE.getDayCount("ACT/360");
// ------------------------------------------------------------------------------------------------------------------------------------
private static final CombinedInterpolatorExtrapolator INTERPOLATOR = CombinedInterpolatorExtrapolatorFactory.getInterpolator(ISDA_INTERPOLATOR, FLAT_EXTRAPOLATOR, ISDA_EXTRAPOLATOR);
private final String _name;
private final ZonedDateTime _baseDates; // RW added to aid conversion
// TODO what is this?
private final double _offset;
private final ZonedDateTime[] _curveDates;
private final DoublesCurve _curve;
private final double[] _shiftedTimePoints;
private final double _zeroDiscountFactor;
private final int _n;
// ------------------------------------------------------------------------------------------------------------------------------------
/**
* A ISDA model zero default curve with ACT/365 day-count convention. This can take in the output from the native ISDA yield curve construction model
* @param name The curve name
* @param baseDate base date to convert other (future) dates into year fractions using the day-count convention
* @param curveDates The dates of points on the curve
* @param rates The zero default rates at points on the curve. <b>Note:</b>These as annually compounded rates
* @param offset TODO find out what this does
*/
public ISDADateCurve(final String name, final ZonedDateTime baseDate, final ZonedDateTime[] curveDates, final double[] rates, final double offset) {
this(name, baseDate, curveDates, rates, offset, ACT_365);
}
/**
* A ISDA model zero default curve
* @param name The curve name
* @param baseDate base date to convert other (future) dates into year fractions using the day-count convention
* @param curveDates The dates of points on the curve
* @param rates The zero default rates at points on the curve. <b>Note:</b>These as annually compounded rates
* @param offset TODO find out what this does
* @param dayCount The day-count convention
*/
public ISDADateCurve(final String name, final ZonedDateTime baseDate, final ZonedDateTime[] curveDates, final double[] rates, final double offset, final DayCount dayCount) {
super(name);
ArgumentChecker.notNull(name, "name");
ArgumentChecker.notNull(baseDate, "base date");
ArgumentChecker.noNulls(curveDates, "curve dates");
ArgumentChecker.notEmpty(rates, "rates");
ArgumentChecker.notNull(dayCount, "day count");
_n = curveDates.length;
ArgumentChecker.isTrue(_n != 0, "Data arrays were empty");
// TODO why is this test commented out?
// ArgumentChecker.isTrue(_n == rates.length, "Have {} rates for {} dates", rates.length, _n);
_baseDates = baseDate;
_name = name;
_offset = offset;
_curveDates = new ZonedDateTime[_n];
System.arraycopy(curveDates, 0, _curveDates, 0, _n);
final double[] times = new double[_n];
final double[] continuousRates = new double[_n];
_shiftedTimePoints = new double[_n];
for (int i = 0; i < _n; i++) {
// Convert the ZonedDateTimes to doubles
final double dayCountFraction = dayCount.getDayCountFraction(baseDate, curveDates[i]);
times[i] = dayCountFraction;
// Convert the discrete rates to continuous ones
continuousRates[i] = new PeriodicInterestRate(rates[i], 1).toContinuous().getRate();
_shiftedTimePoints[i] = dayCountFraction + _offset;
}
// Choose interpolation/extrapolation to match the behavior of curves in the ISDA CDS reference code
if (_n > 1) {
_curve = InterpolatedDoublesCurve.fromSorted(times, continuousRates, INTERPOLATOR);
} else {
_curve = ConstantDoublesCurve.from(continuousRates[0]); // Unless the curve is flat, in which case use a constant curve
}
_zeroDiscountFactor = Math.exp(_offset * getInterestRate(0.0));
}
/**
* A ISDA model zero default curve
* @param name The curve name
* @param curveDates The dates of points on the curve
* @param times Time in years to points on the curve (these should have been calculated from a base date using some day-count convention)
* @param rates The zero default rates at points on the curve. <b>Note:</b>These are continually compounded rates, while the other constructors take
* annually compounded rates
* @param offset TODO find out what this does
*/
public ISDADateCurve(final String name, final ZonedDateTime[] curveDates, final double[] times, final double[] rates, final double offset) {
super(name);
ArgumentChecker.notNull(name, "name");
ArgumentChecker.noNulls(curveDates, "curve dates");
ArgumentChecker.notEmpty(times, "times");
ArgumentChecker.notEmpty(rates, "rates");
_n = curveDates.length;
ArgumentChecker.isTrue(_n != 0, "Data arrays were empty");
// TODO why commented out?
// ArgumentChecker.isTrue(_n == times.length, "Have {} times for {} dates", times.length, _n);
// ArgumentChecker.isTrue(_n == rates.length, "Have {} rates for {} dates", rates.length, _n);
_baseDates = null;
_name = name;
_offset = offset;
_curveDates = new ZonedDateTime[_n];
System.arraycopy(curveDates, 0, _curveDates, 0, _n);
// Choose interpolation/extrapolation to match the behavior of curves in the ISDA CDS reference code
if (_n > 1) {
_curve = InterpolatedDoublesCurve.fromSorted(times, rates, INTERPOLATOR);
} else {
_curve = ConstantDoublesCurve.from(rates[0]); // Unless the curve is flat, in which case use a constant curve
}
_shiftedTimePoints = new double[times.length];
for (int i = 0; i < times.length; ++i) {
_shiftedTimePoints[i] = times[i] + _offset;
}
_zeroDiscountFactor = Math.exp(_offset * getInterestRate(0.0));
}
// ------------------------------------------------------------------------------------------------------------------------------------
/**
*
* @return The dates of points on the curve
*/
public ZonedDateTime[] getCurveDates() {
return _curveDates;
}
public ZonedDateTime getBaseDate() {
return _baseDates;
}
@Override
public String getName() {
return _name;
}
/**
* The zero default rate to time t
* @param t time in years
* @return The zero default rate as a fraction
*/
@Override
public double getInterestRate(final Double t) {
return _curve.getYValue(t - _offset);
}
/**
* Get the time (in years) of point m on the curve (indexed from zero)
* @param m the index
* @return the time (in years)
*/
public double getTimenode(final int m) {
return _curve.getXData()[m];
}
/**
* Get the zero default rate of point m on the curve (indexed from zero)
* @param m the index
* @return The zero default rate
*/
public double getInterestRate(final int m) {
return _curve.getYData()[m];
}
/**
* The survival probability
* @param t time in years
* @return The survival probability to time t
*/
@Override
public double getDiscountFactor(final double t) {
return Math.exp((_offset - t) * getInterestRate(t)) / _zeroDiscountFactor;
}
/**
* get the shifted time points
* @return The shifted time points
*/
public double[] getTimePoints() {
return _shiftedTimePoints;
}
/**
*
* @return The underlying curve
*/
public DoublesCurve getCurve() {
return _curve;
}
/**
*
* @return The offset
*/
public double getOffset() {
return _offset;
}
/**
* This is 1.0 unless an offset is used, in which case it is Math.exp(offset * H(-offset))
* @return Zero Discount Factor
*/
public double getZeroDiscountFactor() {
return _zeroDiscountFactor;
}
/**
*
* @return Number of points on curve
*/
public int getNumberOfCurvePoints() {
return _n;
}
// ------------------------------------------------------------------------------------------------------------------------------------
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ISDADateCurve[name=");
sb.append(_name);
sb.append(", offset=");
sb.append(_offset);
sb.append(", curve dates=");
sb.append(Arrays.asList(_curveDates));
sb.append(", shifted time points=");
sb.append(Arrays.asList(_shiftedTimePoints));
sb.append(", interpolator=");
sb.append(INTERPOLATOR);
sb.append("]");
return sb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(_curveDates);
result = prime * result + _name.hashCode();
result = prime * result + Arrays.hashCode(_shiftedTimePoints);
long temp;
temp = Double.doubleToLongBits(_zeroDiscountFactor);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ISDADateCurve)) {
return false;
}
final ISDADateCurve other = (ISDADateCurve) obj;
if (!ObjectUtils.equals(_name, other._name)) {
return false;
}
if (Double.compare(_zeroDiscountFactor, other._zeroDiscountFactor) != 0) {
return false;
}
if (!Arrays.equals(_curveDates, other._curveDates)) {
return false;
}
if (!Arrays.equals(_shiftedTimePoints, other._shiftedTimePoints)) {
return false;
}
return true;
}
@Override
public double[] getInterestRateParameterSensitivity(final double time) {
throw new NotImplementedException();
}
@Override
public int getNumberOfParameters() {
throw new NotImplementedException();
}
@Override
public List<String> getUnderlyingCurvesNames() {
throw new NotImplementedException();
}
@Override
public double getForwardRate(final double t) {
throw new NotImplementedException();
}
}