/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.convention.daycount;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.Validate;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.temporal.JulianFields;
import org.threeten.bp.temporal.TemporalAdjusters;
import com.opengamma.financial.convention.StubType;
/**
* The 'Actual/Actual ICMA' day count.
*/
public class ActualActualICMA extends ActualTypeDayCount {
/** Serialization version. */
private static final long serialVersionUID = 1L;
@Override
public double getDayCountFraction(final LocalDate firstDate, final LocalDate secondDate) {
throw new NotImplementedException("Cannot get daycount fraction; need information about the coupon and payment frequency");
}
@Override
public double getAccruedInterest(final LocalDate previousCouponDate, final LocalDate date, final LocalDate nextCouponDate, final double coupon, final double paymentsPerYear) {
return getAccruedInterest(previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear, StubType.NONE);
}
/**
* Computes the accrued interest for a specific stub-type.
* @param previousCouponDate the previous coupon date, not null
* @param date the evaluated coupon date, not null
* @param nextCouponDate the next coupon date, not null
* @param coupon the coupon value
* @param paymentsPerYear the number of payments per year, one, two, three, four, six or twelve
* @param stubType The stub type.
* @return The accrued interest.
*/
public double getAccruedInterest(final ZonedDateTime previousCouponDate, final ZonedDateTime date, final ZonedDateTime nextCouponDate, final double coupon, final double paymentsPerYear,
final StubType stubType) {
return getAccruedInterest(previousCouponDate.toLocalDate(), date.toLocalDate(), nextCouponDate.toLocalDate(), coupon, paymentsPerYear, stubType);
}
public double getAccruedInterest(final LocalDate previousCouponDate, final LocalDate date, final LocalDate nextCouponDate, final double coupon, final double paymentsPerYear,
final StubType stubType) {
testDates(previousCouponDate, date, nextCouponDate);
Validate.notNull(stubType, "stub type");
long daysBetween, daysBetweenCoupons;
final long previousCouponDateJulian = previousCouponDate.getLong(JulianFields.MODIFIED_JULIAN_DAY);
final long nextCouponDateJulian = nextCouponDate.getLong(JulianFields.MODIFIED_JULIAN_DAY);
final long dateJulian = date.getLong(JulianFields.MODIFIED_JULIAN_DAY);
final int months = (int) (12 / paymentsPerYear);
switch (stubType) {
case NONE: {
daysBetween = dateJulian - previousCouponDateJulian;
daysBetweenCoupons = nextCouponDate.getLong(JulianFields.MODIFIED_JULIAN_DAY) - previousCouponDateJulian;
return coupon * daysBetween / daysBetweenCoupons / paymentsPerYear;
}
case SHORT_START: {
final LocalDate notionalStart = getEOMAdjustedDate(nextCouponDate, nextCouponDate.minusMonths(months));
daysBetweenCoupons = nextCouponDateJulian - notionalStart.getLong(JulianFields.MODIFIED_JULIAN_DAY);
daysBetween = dateJulian - previousCouponDateJulian;
return coupon * daysBetween / daysBetweenCoupons / paymentsPerYear;
}
case LONG_START: {
final long firstNotionalJulian = getEOMAdjustedDate(nextCouponDate, nextCouponDate.minusMonths(months * 2)).getLong(JulianFields.MODIFIED_JULIAN_DAY);
final long secondNotionalJulian = getEOMAdjustedDate(nextCouponDate, nextCouponDate.minusMonths(months)).getLong(JulianFields.MODIFIED_JULIAN_DAY);
final double daysBetweenTwoNotionalCoupons1 = secondNotionalJulian - firstNotionalJulian;
if (dateJulian > secondNotionalJulian) {
daysBetween = dateJulian - secondNotionalJulian;
final long daysBetweenStub = secondNotionalJulian - previousCouponDateJulian;
final double daysBetweenTwoNotionalCoupons2 = nextCouponDateJulian - secondNotionalJulian;
return coupon * (daysBetweenStub / daysBetweenTwoNotionalCoupons1 + daysBetween / daysBetweenTwoNotionalCoupons2) / paymentsPerYear;
}
daysBetween = dateJulian - previousCouponDateJulian;
return coupon * (daysBetween / daysBetweenTwoNotionalCoupons1) / paymentsPerYear;
}
case SHORT_END: {
final LocalDate notionalEnd = getEOMAdjustedDate(previousCouponDate, previousCouponDate.plusMonths(months));
daysBetweenCoupons = notionalEnd.getLong(JulianFields.MODIFIED_JULIAN_DAY) - previousCouponDateJulian;
daysBetween = dateJulian - previousCouponDateJulian;
return coupon * daysBetween / daysBetweenCoupons / paymentsPerYear;
}
case LONG_END: {
final long firstNotionalJulian = getEOMAdjustedDate(previousCouponDate, previousCouponDate.plusMonths(months)).getLong(JulianFields.MODIFIED_JULIAN_DAY);
final long secondNotionalJulian = getEOMAdjustedDate(previousCouponDate, previousCouponDate.plusMonths(2 * months)).getLong(JulianFields.MODIFIED_JULIAN_DAY);
final long daysBetweenPreviousAndFirstNotional = firstNotionalJulian - previousCouponDateJulian;
if (dateJulian < firstNotionalJulian) {
daysBetween = dateJulian - previousCouponDateJulian;
return coupon * daysBetween / daysBetweenPreviousAndFirstNotional / paymentsPerYear;
}
final long daysBetweenStub = dateJulian - firstNotionalJulian;
final double daysBetweenTwoNotionalCoupons = secondNotionalJulian - firstNotionalJulian;
return coupon * (1 + daysBetweenStub / daysBetweenTwoNotionalCoupons) / paymentsPerYear;
}
default:
throw new IllegalArgumentException("Cannot handle stub type " + stubType);
}
}
@Override
public String getName() {
return "Actual/Actual ICMA";
}
// -------------------------------------------------------------------------
/**
* Adjusts the date to the last day of month if necessary.
*
* @param comparison the date to check as to being the last day of month, not null
* @param date the date to adjust, not null
* @return the adjusted date, not null
*/
private static LocalDate getEOMAdjustedDate(final LocalDate comparison, final LocalDate date) {
if (comparison.getDayOfMonth() == comparison.lengthOfMonth()) {
return date.with(TemporalAdjusters.lastDayOfMonth());
}
return date;
}
}