/**
* 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 java.util.Arrays;
import org.apache.commons.lang.Validate;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.financial.convention.StubCalculator;
import com.opengamma.financial.convention.StubType;
import com.opengamma.financial.convention.calendar.Calendar;
/**
* Utility to calculate the accrued interest.
*/
public final class AccruedInterestCalculator {
/**
* Restricted constructor.
*/
private AccruedInterestCalculator() {
}
/**
* Calculates the accrued interest for a {@code ZonedDateTime}.
*
* @param dayCount the day count convention, not null
* @param settlementDate the settlement date, not null
* @param nominalDates the nominalDates, not null, no null elements
* @param coupon the coupon value
* @param paymentsPerYear the number of payments per year, one, two, three, four, six or twelve
* @param isEndOfMonthConvention whether to use end of month rules
* @param exDividendDays the number of ex-dividend days
* @param calendar The working day calendar to be used in calculating ex-dividend dates, not null
* @return the accrued interest
*/
public static double getAccruedInterest(final DayCount dayCount, final ZonedDateTime settlementDate, final ZonedDateTime[] nominalDates, final double coupon, final int paymentsPerYear,
final boolean isEndOfMonthConvention, final int exDividendDays, final Calendar calendar) {
Validate.notNull(dayCount, "day-count");
Validate.notNull(settlementDate, "date");
Validate.noNullElements(nominalDates, "nominalDates");
Validate.notNull(calendar, "calendar");
Validate.isTrue(paymentsPerYear > 0);
Validate.isTrue(exDividendDays >= 0);
final int i = Arrays.binarySearch(nominalDates, settlementDate);
if (i > 0) {
return 0;
}
final int index = -i - 2;
final int length = nominalDates.length;
Validate.isTrue(index >= 0, "Settlement date is before first accrual date");
Validate.isTrue(index < length, "Settlement date is after maturity date");
final double accruedInterest = getAccruedInterest(dayCount, index, length, nominalDates[index], settlementDate, nominalDates[index + 1], coupon, paymentsPerYear, isEndOfMonthConvention);
ZonedDateTime exDividendDate = nominalDates[index + 1];
for (int j = 0; j < exDividendDays; j++) {
while (!calendar.isWorkingDay(exDividendDate.toLocalDate())) {
exDividendDate = exDividendDate.minusDays(1);
}
exDividendDate = exDividendDate.minusDays(1);
}
if (exDividendDays != 0 && exDividendDate.isBefore(settlementDate)) {
return accruedInterest - coupon;
}
return accruedInterest;
}
/**
* Calculates the accrued interest for a {@code ZonedDateTime}.
*
* @param dayCount the day count convention, not null
* @param settlementDate the settlement date, not null
* @param nominalDates the nominalDates, not null, no null elements
* @param coupon the coupon value
* @param paymentsPerYear the number of payments per year, one, two, three, four, six or twelve
* @param isEndOfMonthConvention whether to use end of month rules
* @param exDividendDays the number of ex-dividend days
* @param index The index of the previous coupon in the nominalDates array
* @param calendar The working day calendar to be used in calculating ex-dividend dates, not null
* @return the accrued interest
*/
public static double getAccruedInterest(final DayCount dayCount, final ZonedDateTime settlementDate, final ZonedDateTime[] nominalDates, final double coupon, final double paymentsPerYear,
final boolean isEndOfMonthConvention, final int exDividendDays, final int index, final Calendar calendar) {
Validate.notNull(dayCount, "day-count");
Validate.notNull(settlementDate, "date");
Validate.noNullElements(nominalDates, "nominalDates");
Validate.notNull(calendar, "calendar");
Validate.isTrue(paymentsPerYear > 0);
Validate.isTrue(exDividendDays >= 0);
final int length = nominalDates.length;
Validate.isTrue(index >= 0 && index < length);
final double accruedInterest = getAccruedInterest(dayCount, index, length, nominalDates[index], settlementDate, nominalDates[index + 1], coupon, paymentsPerYear, isEndOfMonthConvention);
ZonedDateTime exDividendDate = nominalDates[index + 1];
for (int i = 0; i < exDividendDays; i++) {
while (!calendar.isWorkingDay(exDividendDate.toLocalDate())) {
exDividendDate = exDividendDate.minusDays(1);
}
exDividendDate = exDividendDate.minusDays(1);
}
if (exDividendDays != 0 && exDividendDate.isBefore(settlementDate)) {
return accruedInterest - coupon;
}
return accruedInterest;
}
/**
* Calculates the accrued interest for a {@code LocalDate}.
*
* @param dayCount the day count convention, not null
* @param settlementDate the settlement date, not null
* @param nominalDates the nominalDates, not null, no null elements
* @param coupon the coupon value
* @param paymentsPerYear the number of payments per year, one, two, three, four, six or twelve
* @param isEndOfMonthConvention whether to use end of month rules
* @param exDividendDays the number of ex-dividend days
* @param calendar The working day calendar to be used in calculating ex-dividend dates, not null
* @return the accrued interest
*/
//TODO one where you can pass in array of coupons
public static double getAccruedInterest(final DayCount dayCount, final LocalDate settlementDate, final LocalDate[] nominalDates, final double coupon, final double paymentsPerYear,
final boolean isEndOfMonthConvention, final int exDividendDays, final Calendar calendar) {
Validate.notNull(dayCount, "day-count");
Validate.notNull(settlementDate, "date");
Validate.noNullElements(nominalDates, "nominalDates");
Validate.notNull(calendar, "calendar");
Validate.isTrue(paymentsPerYear > 0);
Validate.isTrue(exDividendDays >= 0);
final int i = Arrays.binarySearch(nominalDates, settlementDate);
if (i > 0) {
return 0;
}
final int index = -i - 2;
final int length = nominalDates.length;
if (index < 0) {
throw new IllegalArgumentException("Settlement date is before first accrual date");
}
if (index == length) {
throw new IllegalArgumentException("Settlement date is after maturity date");
}
final ZonedDateTime previousCouponDate = nominalDates[index].atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime date = settlementDate.atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime nextCouponDate = nominalDates[index + 1].atStartOfDay(ZoneOffset.UTC);
final double accruedInterest = getAccruedInterest(dayCount, index, length, previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear, isEndOfMonthConvention);
LocalDate exDividendDate = nominalDates[index + 1];
for (int j = 0; j < exDividendDays; j++) {
while (!calendar.isWorkingDay(exDividendDate)) {
exDividendDate = exDividendDate.minusDays(1);
}
exDividendDate = exDividendDate.minusDays(1);
}
if (exDividendDays != 0 && exDividendDate.isBefore(settlementDate)) {
return accruedInterest - coupon;
}
return accruedInterest;
}
/**
* Calculates the accrued interest for a {@code LocalDate}.
*
* @param dayCount the day count convention, not null
* @param settlementDate the settlement date, not null
* @param nominalDates the nominalDates, not null, no null elements
* @param coupon the coupon value
* @param paymentsPerYear the number of payments per year, one, two, three, four, six or twelve
* @param isEndOfMonthConvention whether to use end of month rules
* @param exDividendDays the number of ex-dividend days
* @param index The index of the previous coupon in the nominalDates
* @param calendar The working day calendar to be used in calculating ex-dividend dates, not null
* @return the accrued interest
*/
public static double getAccruedInterest(final DayCount dayCount, final LocalDate settlementDate, final LocalDate[] nominalDates, final double coupon, final double paymentsPerYear,
final boolean isEndOfMonthConvention, final int exDividendDays, final int index, final Calendar calendar) {
Validate.notNull(dayCount, "day-count");
Validate.notNull(settlementDate, "date");
Validate.noNullElements(nominalDates, "nominalDates");
Validate.notNull(calendar, "calendar");
Validate.isTrue(paymentsPerYear > 0);
Validate.isTrue(exDividendDays >= 0);
final int length = nominalDates.length;
Validate.isTrue(index >= 0 && index < length);
final ZonedDateTime previousCouponDate = nominalDates[index].atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime date = settlementDate.atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime nextCouponDate = nominalDates[index + 1].atStartOfDay(ZoneOffset.UTC);
double accruedInterest;
if (date.isAfter(nextCouponDate)) {
accruedInterest = 0;
} else {
accruedInterest = getAccruedInterest(dayCount, index, length, previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear, isEndOfMonthConvention);
}
LocalDate exDividendDate = nominalDates[index + 1];
for (int i = 0; i < exDividendDays; i++) {
while (!calendar.isWorkingDay(exDividendDate)) {
exDividendDate = exDividendDate.minusDays(1);
}
exDividendDate = exDividendDate.minusDays(1);
}
if (exDividendDays != 0 && exDividendDate.isBefore(settlementDate)) {
return accruedInterest - coupon;
}
return accruedInterest;
}
/**
* Calculates the accrued interest for a {@code LocalDate}.
*
* @param dayCount the day count convention, not null
* @param settlementDate the settlement date, not null
* @param nominalDates the nominalDates, not null, no null elements
* @param settlementDates the settlement dates, not null, no null elements
* @param coupon the coupon value
* @param paymentsPerYear the number of payments per year, one, two, three, four, six or twelve
* @param isEndOfMonthConvention whether to use end of month rules
* @param exDividendDays the number of ex-dividend days
* @param index The index of the previous coupon in the nominalDates
* @param calendar The working day calendar used to calculate the ex-dividend date, not null
* @return the accrued interest
*/
public static double getAccruedInterest(final DayCount dayCount, final LocalDate settlementDate, final LocalDate[] nominalDates, final LocalDate[] settlementDates, final double coupon,
final double paymentsPerYear, final boolean isEndOfMonthConvention, final int exDividendDays, final int index, final Calendar calendar) {
Validate.notNull(dayCount, "day-count");
Validate.notNull(settlementDate, "date");
Validate.notNull(calendar, "calendar");
Validate.noNullElements(nominalDates, "nominalDates");
Validate.noNullElements(settlementDates, "settlementDates");
Validate.isTrue(paymentsPerYear > 0);
Validate.isTrue(exDividendDays >= 0);
final int length = nominalDates.length;
Validate.isTrue(index >= 0 && index < length);
final LocalDate previousCouponDate = nominalDates[index];
final LocalDate nextCouponDate = nominalDates[index + 1];
double accruedInterest;
if (settlementDate.isAfter(nextCouponDate)) {
if (settlementDate.isBefore(settlementDates[index + 1])) {
accruedInterest = coupon;
} else {
accruedInterest = 0;
}
} else {
accruedInterest = getAccruedInterest(dayCount, index, length, previousCouponDate, settlementDate, nextCouponDate, coupon, paymentsPerYear, isEndOfMonthConvention);
}
LocalDate exDividendDate = nominalDates[index + 1];
for (int i = 0; i < exDividendDays; i++) {
while (!calendar.isWorkingDay(exDividendDate)) {
exDividendDate = exDividendDate.minusDays(1);
}
exDividendDate = exDividendDate.minusDays(1);
}
if (exDividendDays != 0 && exDividendDate.isBefore(settlementDate)) {
return accruedInterest - coupon;
}
return accruedInterest;
}
public static double getAccruedInterest(final DayCount dayCount, final int index, final int length, final ZonedDateTime previousCouponDate, final ZonedDateTime date,
final ZonedDateTime nextCouponDate, final double coupon, final double paymentsPerYear, final boolean isEndOfMonthConvention) {
if (dayCount instanceof ActualActualICMANormal) {
if (isEndOfMonthConvention) {
throw new IllegalArgumentException("Inconsistent definition; asked for accrual with EOM convention but are not using Actual/Actual ICMA");
}
final StubType stubType = getStubType(index, length, previousCouponDate, nextCouponDate, paymentsPerYear, isEndOfMonthConvention);
return ((ActualActualICMANormal) dayCount).getAccruedInterest(previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear, stubType);
} else if (dayCount instanceof ActualActualICMA) {
final StubType stubType = getStubType(index, length, previousCouponDate, nextCouponDate, paymentsPerYear, isEndOfMonthConvention);
return ((ActualActualICMA) dayCount).getAccruedInterest(previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear, stubType);
} else if (dayCount instanceof ThirtyUThreeSixty) {
return ((ThirtyUThreeSixty) dayCount).getAccruedInterest(previousCouponDate, date, coupon, isEndOfMonthConvention);
}
return dayCount.getAccruedInterest(previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear);
}
public static double getAccruedInterest(final DayCount dayCount, final int index, final int length, final LocalDate previousCouponDate, final LocalDate date,
final LocalDate nextCouponDate, final double coupon, final double paymentsPerYear, final boolean isEndOfMonthConvention) {
if (dayCount instanceof ActualActualICMANormal) {
if (isEndOfMonthConvention) {
throw new IllegalArgumentException("Inconsistent definition; asked for accrual with EOM convention but are not using Actual/Actual ICMA");
}
final StubType stubType = getStubType(index, length, previousCouponDate, nextCouponDate, paymentsPerYear, isEndOfMonthConvention);
return ((ActualActualICMANormal) dayCount).getAccruedInterest(previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear, stubType);
} else if (dayCount instanceof ActualActualICMA) {
final StubType stubType = getStubType(index, length, previousCouponDate, nextCouponDate, paymentsPerYear, isEndOfMonthConvention);
return ((ActualActualICMA) dayCount).getAccruedInterest(previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear, stubType);
} else if (dayCount instanceof ThirtyUThreeSixty) {
return ((ThirtyUThreeSixty) dayCount).getAccruedInterest(previousCouponDate, date, coupon, isEndOfMonthConvention);
}
return dayCount.getAccruedInterest(previousCouponDate, date, nextCouponDate, coupon, paymentsPerYear);
}
private static StubType getStubType(final int index, final int length, final ZonedDateTime previousCouponDate, final ZonedDateTime nextCouponDate, final double paymentsPerYear,
final boolean isEndOfMonthConvention) {
StubType stubType;
if (index == 0) {
LocalDate[] schedule = new LocalDate[] {previousCouponDate.toLocalDate(), nextCouponDate.toLocalDate()};
stubType = StubCalculator.getStartStubType(schedule, paymentsPerYear, isEndOfMonthConvention);
} else if (index == length - 2) {
LocalDate[] schedule = new LocalDate[] {previousCouponDate.toLocalDate(), nextCouponDate.toLocalDate()};
stubType = StubCalculator.getEndStubType(schedule, paymentsPerYear, isEndOfMonthConvention);
} else {
stubType = StubType.NONE;
}
return stubType;
}
private static StubType getStubType(final int index, final int length, final LocalDate previousCouponDate, final LocalDate nextCouponDate, final double paymentsPerYear,
final boolean isEndOfMonthConvention) {
StubType stubType;
if (index == 0) {
LocalDate[] schedule = new LocalDate[] {previousCouponDate, nextCouponDate};
stubType = StubCalculator.getStartStubType(schedule, paymentsPerYear, isEndOfMonthConvention);
} else if (index == length - 2) {
LocalDate[] schedule = new LocalDate[] {previousCouponDate, nextCouponDate};
stubType = StubCalculator.getEndStubType(schedule, paymentsPerYear, isEndOfMonthConvention);
} else {
stubType = StubType.NONE;
}
return stubType;
}
}