/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.schedule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.temporal.TemporalAdjusters;
import com.opengamma.analytics.financial.instrument.index.GeneratorDeposit;
import com.opengamma.analytics.financial.instrument.index.IborIndex;
import com.opengamma.financial.convention.businessday.BusinessDayConvention;
import com.opengamma.financial.convention.businessday.FollowingBusinessDayConvention;
import com.opengamma.financial.convention.businessday.PrecedingBusinessDayConvention;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.daycount.DayCount;
import com.opengamma.financial.convention.frequency.Frequency;
import com.opengamma.financial.convention.frequency.PeriodFrequency;
import com.opengamma.financial.convention.frequency.SimpleFrequency;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.time.DateUtils;
/**
* Utility to calculate schedules.
*/
public final class ScheduleCalculator {
/**
* A singleton empty array.
*/
private static final ZonedDateTime[] EMPTY_ARRAY = new ZonedDateTime[0];
/**
* Restricted constructor.
*/
private ScheduleCalculator() {
}
// Already reviewed
/**
* Return a good business date computed from a given date and shifted by a certain number of business days.
* If the number of shift days is 0, the return date is the next business day.
* If the number of shift days is non-zero (positive or negative), a 0 shift is first applied and then a one business day shift is applied as many time as the absolute value of the shift.
* If the shift is positive, the one business day is to the future., if the shift is negative, the one business day is to the past.
* @param date The initial date.
* @param shiftDays The number of days of the adjustment. Can be negative or positive.
* @param calendar The calendar representing the good business days.
* @return The adjusted date.
*/
public static ZonedDateTime getAdjustedDate(final ZonedDateTime date, final int shiftDays, final Calendar calendar) {
ArgumentChecker.notNull(date, "date");
ArgumentChecker.notNull(calendar, "calendar");
ZonedDateTime result = date;
while (!calendar.isWorkingDay(result.toLocalDate())) {
result = result.plusDays(1);
}
if (shiftDays > 0) {
for (int loopday = 0; loopday < shiftDays; loopday++) {
result = result.plusDays(1);
while (!calendar.isWorkingDay(result.toLocalDate())) {
result = result.plusDays(1);
}
}
} else {
for (int loopday = 0; loopday < -shiftDays; loopday++) {
result = result.minusDays(1);
while (!calendar.isWorkingDay(result.toLocalDate())) {
result = result.minusDays(1);
}
}
}
return result;
}
/**
* Return a good business dates computed from given array of date and shifted by a certain number of business days (one return date for each input date).
* If the number of shift days is 0, the return date is the next business day.
* If the number of shift days is non-zero (positive or negative), a 0 shift is first applied and then a one business day shift is applied as many time as the absolute value of the shift.
* If the shift is positive, the one business day is to the future., if the shift is negative, the one business day is to the past.
* @param dates The initial dates.
* @param shiftDays The number of days of the adjustment. Can be negative or positive.
* @param calendar The calendar representing the good business days.
* @return The adjusted dates.
*/
public static ZonedDateTime[] getAdjustedDate(final ZonedDateTime[] dates, final int shiftDays, final Calendar calendar) {
final int nbDates = dates.length;
final ZonedDateTime[] result = new ZonedDateTime[nbDates];
for (int loopdate = 0; loopdate < nbDates; loopdate++) {
result[loopdate] = getAdjustedDate(dates[loopdate], shiftDays, calendar);
}
return result;
}
/**
* Return a good business date computed from a given date and shifted by a certain number of business days. The number of business days is given by the getDays part of a peeriod.
* If the number of shift days is 0, the return date is the next business day.
* If the number of shift days is non-zero (positive or negative), a 0 shift is first applied and then a one business day shift is applied as many time as the absolute value of the shift.
* If the shift is positive, the one business day is to the future., if the shift is negative, the one business day is to the past.
* @param date The initial date.
* @param shiftDays The number of days of the adjustment as a period.
* @param calendar The calendar representing the good business days.
* @return The adjusted dates.
*/
public static ZonedDateTime getAdjustedDate(final ZonedDateTime date, final Period shiftDays, final Calendar calendar) {
ArgumentChecker.notNull(shiftDays, "shift days");
return getAdjustedDate(date, shiftDays.getDays(), calendar);
}
/**
* Return a good business date computed from a given date and shifted by a certain number of business days.
* This version uses LocalDate.
* If the number of shift days is 0, the return date is the next business day.
* If the number of shift days is non-zero (positive or negative), a 0 shift is first applied and then a one business day shift is applied as many time as the absolute value of the shift.
* If the shift is positive, the one business day is to the future., if the shift is negative, the one business day is to the past.
* @param date The initial date.
* @param shiftDays The number of days of the adjustment. Can be negative or positive.
* @param calendar The calendar representing the good business days.
* @return The adjusted dates.
*/
public static LocalDate getAdjustedDate(final LocalDate date, final int shiftDays, final Calendar calendar) {
ArgumentChecker.notNull(date, "date");
ArgumentChecker.notNull(calendar, "calendar");
LocalDate result = date;
while (!calendar.isWorkingDay(result)) {
result = result.plusDays(1);
}
if (shiftDays > 0) {
for (int loopday = 0; loopday < shiftDays; loopday++) {
result = result.plusDays(1);
while (!calendar.isWorkingDay(result)) {
result = result.plusDays(1);
}
}
} else {
for (int loopday = 0; loopday < -shiftDays; loopday++) {
result = result.minusDays(1);
while (!calendar.isWorkingDay(result)) {
result = result.minusDays(1);
}
}
}
return result;
}
/**
* Compute the end date of a period from the start date, the tenor and the conventions without end-of-month convention.
* @param startDate The period start date.
* @param tenor The period tenor.
* @param convention The business day convention.
* @param calendar The calendar.
* @return The end date.
*/
public static ZonedDateTime getAdjustedDate(final ZonedDateTime startDate, final Period tenor, final BusinessDayConvention convention, final Calendar calendar) {
ArgumentChecker.notNull(startDate, "start date");
ArgumentChecker.notNull(convention, "convention");
ArgumentChecker.notNull(calendar, "calendar");
ArgumentChecker.notNull(tenor, "tenor");
final ZonedDateTime endDate = startDate.plus(tenor); // Unadjusted date.
return convention.adjustDate(calendar, endDate); // Adjusted by Business day convention
}
/**
* Compute the end date of a period from the start date, the tenor and the conventions.
* @param startDate The period start date.
* @param tenor The period tenor.
* @param convention The business day convention.
* @param calendar The calendar.
* @param endOfMonthRule True if end-of-month rule applies, false if it does not.
* The rule applies when the start date is the last business day of the month and the period is a number of months or years, not days or weeks.
* When the rule applies, the end date is the last business day of the month.
* @return The end date.
*/
public static ZonedDateTime getAdjustedDate(final ZonedDateTime startDate, final Period tenor, final BusinessDayConvention convention, final Calendar calendar,
final boolean endOfMonthRule) {
ArgumentChecker.notNull(startDate, "Start date");
ArgumentChecker.notNull(convention, "Convention");
ArgumentChecker.notNull(calendar, "Calendar");
ArgumentChecker.notNull(tenor, "Tenor");
final ZonedDateTime endDate = startDate.plus(tenor); // Unadjusted date.
// Adjusted to month-end: when start date is last business day of the month, the end date is the last business day of the month.
final boolean isStartDateEOM = (startDate.getMonth() != getAdjustedDate(startDate, 1, calendar).getMonth());
if ((tenor.getDays() == 0) & (endOfMonthRule) & (isStartDateEOM)) {
final BusinessDayConvention preceding = new PrecedingBusinessDayConvention();
return preceding.adjustDate(calendar, endDate.with(TemporalAdjusters.lastDayOfMonth()));
}
return convention.adjustDate(calendar, endDate); // Adjusted by Business day convention
}
/**
* Compute the end date of a period from the start date, the tenor and the conventions.
* @param startDate The period start date.
* @param tenor The period tenor.
* @param generator The deposit generator with the required conventions.
* @return The end date.
*/
public static ZonedDateTime getAdjustedDate(final ZonedDateTime startDate, final Period tenor, final GeneratorDeposit generator) {
ArgumentChecker.notNull(generator, "Generator");
return getAdjustedDate(startDate, tenor, generator.getBusinessDayConvention(), generator.getCalendar(), generator.isEndOfMonth());
}
/**
* Compute the end date of a period from the start date, a period and a Ibor index. The index is used for the conventions.
* @param startDate The period start date.
* @param tenor The period tenor.
* @param index The Ibor index.
* @param calendar The holiday calendar.
* @return The end date.
*/
public static ZonedDateTime getAdjustedDate(final ZonedDateTime startDate, final Period tenor, final IborIndex index, final Calendar calendar) {
ArgumentChecker.notNull(index, "Index");
return getAdjustedDate(startDate, tenor, index.getBusinessDayConvention(), calendar, index.isEndOfMonth());
}
/**
* Compute the end date of a period from the start date and a Ibor index. The period between the start date and the end date is the index tenor.
* @param startDate The period start date.
* @param index The Ibor index.
* @param calendar The holiday calendar.
* @return The end date.
*/
public static ZonedDateTime getAdjustedDate(final ZonedDateTime startDate, final IborIndex index, final Calendar calendar) {
ArgumentChecker.notNull(index, "Index");
return getAdjustedDate(startDate, index.getTenor(), index.getBusinessDayConvention(), calendar, index.isEndOfMonth());
}
/**
* Compute the end dates of periods from the start dates and a Ibor index. The period between the start date and the end date is the index tenor.
* There is one return date for each input date.
* @param startDates The period start dates.
* @param index The Ibor index.
* @param calendar The holiday calendar.
* @return The end dates.
*/
public static ZonedDateTime[] getAdjustedDate(final ZonedDateTime[] startDates, final IborIndex index, final Calendar calendar) {
final int nbDates = startDates.length;
final ZonedDateTime[] result = new ZonedDateTime[nbDates];
for (int loopdate = 0; loopdate < nbDates; loopdate++) {
result[loopdate] = getAdjustedDate(startDates[loopdate], index, calendar);
}
return result;
}
/**
* Compute a schedule of unadjusted dates from a start date, an end date and the period between dates.
* @param startDate The start date.
* @param endDate The end date.
* @param tenorPeriod The period between each date.
* @param stubShort In case the the periods do not fit exactly between start and end date, is the remaining interval shorter (true) or longer (false) than the requested period.
* @param fromEnd The dates in the schedule can be computed from the end date (true) or from the start date (false).
* @return The date schedule (not including the start date).
*/
public static ZonedDateTime[] getUnadjustedDateSchedule(final ZonedDateTime startDate, final ZonedDateTime endDate, final Period tenorPeriod, final boolean stubShort,
final boolean fromEnd) {
ArgumentChecker.notNull(startDate, "Start date");
ArgumentChecker.notNull(endDate, "End date");
ArgumentChecker.notNull(tenorPeriod, "Period tenor");
ArgumentChecker.isTrue(startDate.isBefore(endDate), "Start date should be strictly before end date");
final List<ZonedDateTime> dates = new ArrayList<>();
int nbPeriod = 0;
if (!fromEnd) { // Add the periods from the start date
ZonedDateTime date = startDate.plus(tenorPeriod);
while (date.isBefore(endDate)) { // date is strictly before endDate
dates.add(date);
nbPeriod++;
date = startDate.plus(tenorPeriod.multipliedBy(nbPeriod + 1));
}
if (!stubShort && !date.equals(endDate) && nbPeriod >= 1) { // For long stub the last date before end date, if any, is removed.
dates.remove(nbPeriod - 1);
}
dates.add(endDate);
return dates.toArray(EMPTY_ARRAY);
}
// From end - Subtract the periods from the end date
ZonedDateTime date = endDate;
while (date.isAfter(startDate)) { // date is strictly after startDate
dates.add(date);
nbPeriod++;
date = endDate.minus(tenorPeriod.multipliedBy(nbPeriod));
}
if (!stubShort && !date.equals(startDate) && nbPeriod >= 1) { // For long stub the last date before end date, if any, is removed.
dates.remove(nbPeriod - 1);
}
Collections.sort(dates); // To obtain the dates in chronological order.
return dates.toArray(EMPTY_ARRAY);
}
/**
* Adjust an array of date with a given convention and EOM flag.
* @param dates The array of unadjusted dates.
* @param convention The business day convention.
* @param calendar The calendar.
* @param eomApply The flag indicating if the EOM apply, i.e. if the flag is true, the adjusted date is the last business day of the unadjusted date.
* @return The adjusted dates.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime[] dates, final BusinessDayConvention convention, final Calendar calendar,
final boolean eomApply) {
final ZonedDateTime[] result = new ZonedDateTime[dates.length];
if (eomApply) {
final BusinessDayConvention precedingDBC = new PrecedingBusinessDayConvention(); //To ensure that the date stays in the current month.
for (int loopdate = 0; loopdate < dates.length; loopdate++) {
result[loopdate] = precedingDBC.adjustDate(calendar, dates[loopdate].with(TemporalAdjusters.lastDayOfMonth()));
}
return result;
}
for (int loopdate = 0; loopdate < dates.length; loopdate++) {
result[loopdate] = convention.adjustDate(calendar, dates[loopdate]);
}
return result;
}
/**
* Compute a schedule of adjusted dates from a start date, an end date and the period between dates.
* @param startDate The start date.
* @param endDate The end date.
* @param schedulePeriod The period between each date in the schedule.
* @param stubShort In case the the periods do not fit exactly between start and end date, is the remaining interval shorter (true) or longer (false) than the requested period.
* @param fromEnd The dates in the schedule can be computed from the end date (true) or from the start date (false).
* @param convention The business day convention.
* @param calendar The calendar.
* @param eomRule Flag indicating if the end-of-month rule should be applied.
* @return The adjusted dates schedule.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final ZonedDateTime endDate, final Period schedulePeriod, final boolean stubShort,
final boolean fromEnd, final BusinessDayConvention convention, final Calendar calendar, final boolean eomRule) {
final ZonedDateTime[] unadjustedDateSchedule = getUnadjustedDateSchedule(startDate, endDate, schedulePeriod, stubShort, fromEnd);
final boolean eomApply = (eomRule && (getAdjustedDate(startDate, 1, calendar).getMonth() != startDate.getMonth()));
return getAdjustedDateSchedule(unadjustedDateSchedule, convention, calendar, eomApply);
}
/**
* Compute a schedule of adjusted dates from a start date, an end date and the period between dates.
* @param startDate The start date.
* @param endDate The end date.
* @param scheduleFrequency The frequency of dates in the schedule.
* @param stubShort In case the the periods do not fit exactly between start and end date, is the remaining interval shorter (true) or longer (false) than the requested period.
* @param fromEnd The dates in the schedule can be computed from the end date (true) or from the start date (false).
* @param convention The business day convention.
* @param calendar The calendar.
* @param eomRule Flag indicating if the end-of-month rule should be applied.
* @return The adjusted dates schedule.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final ZonedDateTime endDate, final Frequency scheduleFrequency,
final boolean stubShort, final boolean fromEnd, final BusinessDayConvention convention, final Calendar calendar, final boolean eomRule) {
ArgumentChecker.notNull(scheduleFrequency, "Schedule frequency");
final Period schedulePeriod = periodFromFrequency(scheduleFrequency);
return getAdjustedDateSchedule(startDate, endDate, schedulePeriod, stubShort, fromEnd, convention, calendar, eomRule);
}
/**
* Compute a schedule of adjusted dates from a start date, total tenor and the period between dates.
* @param startDate The start date.
* @param tenorTotal The total tenor.
* @param tenorPeriod The period between each date.
* @param stubShort In case the the periods do not fit exactly between start and end date, is the remaining interval shorter (true) or longer (false) than the requested period.
* @param fromEnd The dates in the schedule can be computed from the end date (true) or from the start date (false).
* @param convention The business day convention.
* @param calendar The calendar.
* @param eomRule Flag indicating if the end-of-month rule should be applied.
* @return The adjusted dates schedule (not including the start date).
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final Period tenorTotal, final Period tenorPeriod, final boolean stubShort,
final boolean fromEnd, final BusinessDayConvention convention, final Calendar calendar, final boolean eomRule) {
final ZonedDateTime[] unadjustedDateSchedule = getUnadjustedDateSchedule(startDate, startDate.plus(tenorTotal), tenorPeriod, stubShort, fromEnd);
final boolean eomApply = (eomRule && (getAdjustedDate(startDate, 1, calendar).getMonth() != startDate.getMonth()) && (tenorTotal.getDays() == 0));
// Implementation note: the "tenorTotal.getDays() == 0" condition is required as the rule does not apply for period of less than 1 month (like 1 week).
return getAdjustedDateSchedule(unadjustedDateSchedule, convention, calendar, eomApply);
}
/**
* Compute a schedule of adjusted dates from a start date, total tenor and a Ibor index.
* @param startDate The start date.
* @param tenorTotal The total tenor.
* @param stubShort In case the the periods do not fit exactly between start and end date, is the remaining interval shorter (true) or longer (false) than the requested period.
* @param fromEnd The dates in the schedule can be computed from the end date (true) or from the start date (false).
* @param index The related ibor index. The period tenor, business day convention, calendar and EOM rule of the index are used.
* @param calendar The holiday calendar.
* @return The adjusted dates schedule (not including the start date).
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final Period tenorTotal, final boolean stubShort, final boolean fromEnd,
final IborIndex index, final Calendar calendar) {
return getAdjustedDateSchedule(startDate, tenorTotal, index.getTenor(), stubShort, fromEnd, index.getBusinessDayConvention(), calendar,
index.isEndOfMonth());
}
/**
* Convert a Frequency to a Period when possible.
* @param frequency The frequency.
* @return The converted period.
*/
private static Period periodFromFrequency(final Frequency frequency) {
PeriodFrequency periodFrequency;
if (frequency instanceof PeriodFrequency) {
periodFrequency = (PeriodFrequency) frequency;
} else if (frequency instanceof SimpleFrequency) {
periodFrequency = ((SimpleFrequency) frequency).toPeriodFrequency();
} else {
throw new IllegalArgumentException("For the moment can only deal with PeriodFrequency and SimpleFrequency");
}
return periodFrequency.getPeriod();
}
// TODO: review the methods below.
// -------------------------------------------------------------------------
/**
* Calculates the unadjusted date schedule.
*
* @param effectiveDate the effective date, not null
* @param maturityDate the maturity date, not null
* @param frequency how many times a year dates occur, not null
* @return the schedule, not null
*/
public static ZonedDateTime[] getUnadjustedDateSchedule(final ZonedDateTime effectiveDate, final ZonedDateTime maturityDate, final Frequency frequency) {
ArgumentChecker.notNull(effectiveDate, "effective date");
ArgumentChecker.notNull(maturityDate, "maturity date");
ArgumentChecker.notNull(frequency, "frequency");
if (effectiveDate.isAfter(maturityDate)) {
throw new IllegalArgumentException("Effective date was after maturity");
}
return getUnadjustedDateSchedule(effectiveDate, effectiveDate, maturityDate, frequency);
}
/**
* Calculates the unadjusted date schedule.
*
* @param effectiveDate the effective date, not null
* @param accrualDate the accrual date, not null
* @param maturityDate the maturity date, not null
* @param frequency how many times a year dates occur, not null
* @return the schedule, not null
*/
public static ZonedDateTime[] getUnadjustedDateSchedule(final ZonedDateTime effectiveDate, final ZonedDateTime accrualDate, final ZonedDateTime maturityDate,
final Frequency frequency) {
ArgumentChecker.notNull(effectiveDate, "effective date");
ArgumentChecker.notNull(accrualDate, "accrual date");
ArgumentChecker.notNull(maturityDate, "maturity date");
ArgumentChecker.notNull(frequency, "frequency");
if (effectiveDate.isAfter(maturityDate)) {
throw new IllegalArgumentException("Effective date was after maturity");
}
if (accrualDate.isAfter(maturityDate)) {
throw new IllegalArgumentException("Accrual date was after maturity");
}
// TODO what if there's no valid date between accrual date and maturity date?
PeriodFrequency periodFrequency;
if (frequency instanceof PeriodFrequency) {
periodFrequency = (PeriodFrequency) frequency;
} else if (frequency instanceof SimpleFrequency) {
periodFrequency = ((SimpleFrequency) frequency).toPeriodFrequency();
} else {
throw new IllegalArgumentException("For the moment can only deal with PeriodFrequency and SimpleFrequency");
}
final Period period = periodFrequency.getPeriod();
final List<ZonedDateTime> dates = new ArrayList<>();
ZonedDateTime date = effectiveDate; // TODO this is only correct if effective date = accrual date
date = date.plus(period);
while (isWithinSwapLifetime(date, maturityDate)) { // REVIEW: could speed this up by working out how many periods between start and end date?
dates.add(date);
date = date.plus(period);
}
return dates.toArray(EMPTY_ARRAY);
}
//TODO: add doc
public static ZonedDateTime[] getUnadjustedDateSchedule(final ZonedDateTime effectiveDate, final ZonedDateTime accrualDate, final ZonedDateTime maturityDate,
final Period period) {
ArgumentChecker.notNull(effectiveDate, "effective date");
ArgumentChecker.notNull(accrualDate, "accrual date");
ArgumentChecker.notNull(maturityDate, "maturity date");
ArgumentChecker.notNull(period, "period");
if (effectiveDate.isAfter(maturityDate)) {
throw new IllegalArgumentException("Effective date was after maturity");
}
if (accrualDate.isAfter(maturityDate)) {
throw new IllegalArgumentException("Accrual date was after maturity");
}
// TODO what if there's no valid date between accrual date and maturity date?
final List<ZonedDateTime> dates = new ArrayList<>();
int nbPeriod = 1; // M 26-Aug
ZonedDateTime date = effectiveDate; // TODO this is only correct if effective date = accrual date
date = date.plus(period);
while (isWithinSwapLifetime(date, maturityDate)) { // REVIEW: could speed this up by working out how many periods between start and end date?
dates.add(date);
nbPeriod++; // M 26-Aug
date = effectiveDate.plus(period.multipliedBy(nbPeriod)); // M 26-Aug date = date.plus(period);
}
return dates.toArray(EMPTY_ARRAY);
}
// -------------------------------------------------------------------------
/**
* Counts back from maturityDate, filling to equally spaced dates frequency
* times a year until the last date <b>after</b> effective date.
*
* @param effectiveDate the date that terminates to back counting (i.e. the first date is after this date), not null
* @param maturityDate the date to count back from, not null
* @param frequency how many times a year dates occur, not null
* @return the first date after effectiveDate (i.e. effectiveDate is <b>not</b> included to the maturityDate (included)
*/
public static ZonedDateTime[] getBackwardsUnadjustedDateSchedule(final ZonedDateTime effectiveDate, final ZonedDateTime maturityDate, final Frequency frequency) {
ArgumentChecker.notNull(effectiveDate, "effective date");
ArgumentChecker.notNull(maturityDate, "maturity date");
ArgumentChecker.notNull(frequency, "frequency");
if (effectiveDate.isAfter(maturityDate)) {
throw new IllegalArgumentException("Effective date was after maturity");
}
PeriodFrequency periodFrequency;
if (frequency instanceof PeriodFrequency) {
periodFrequency = (PeriodFrequency) frequency;
} else if (frequency instanceof SimpleFrequency) {
periodFrequency = ((SimpleFrequency) frequency).toPeriodFrequency();
} else {
throw new IllegalArgumentException("For the moment can only deal with PeriodFrequency and SimpleFrequency");
}
final Period period = periodFrequency.getPeriod();
final List<ZonedDateTime> dates = new ArrayList<>();
ZonedDateTime date = maturityDate;
// TODO review the tolerance given
while (date.isAfter(effectiveDate) && DateUtils.getExactDaysBetween(effectiveDate, date) > 4.0) {
dates.add(date);
date = date.minus(period);
}
Collections.sort(dates);
return dates.toArray(EMPTY_ARRAY);
}
private static boolean isWithinSwapLifetime(final ZonedDateTime date, final ZonedDateTime maturity) {
// TODO change me urgently
if (date.isBefore(maturity)) {
return true;
}
if (DateUtils.getDaysBetween(date, maturity) < 7) {
return true;
}
return false;
}
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime[] dates, final BusinessDayConvention convention, final Calendar calendar) {
return getAdjustedDateSchedule(dates, convention, calendar, 0);
}
/**
* Return the dates adjusted by a certain number of business days.
* @param dates The initial dates.
* @param convention The business day convention.
* @param calendar The calendar.
* @param settlementDays The number of days of the adjustment. Can be negative or positive.
* @return The adjusted dates.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime[] dates, final BusinessDayConvention convention, final Calendar calendar,
final int settlementDays) {
ArgumentChecker.notEmpty(dates, "dates");
ArgumentChecker.notNull(convention, "convention");
ArgumentChecker.notNull(calendar, "calendar");
final int n = dates.length;
final ZonedDateTime[] result = new ZonedDateTime[n];
for (int i = 0; i < n; i++) {
ZonedDateTime date = convention.adjustDate(calendar, dates[i]);
if (settlementDays > 0) {
for (int loopday = 0; loopday < settlementDays; loopday++) {
date = date.plusDays(1);
while (!calendar.isWorkingDay(date.toLocalDate())) {
date = date.plusDays(1);
}
}
} else {
for (int loopday = 0; loopday < -settlementDays; loopday++) {
date = date.minusDays(1);
while (!calendar.isWorkingDay(date.toLocalDate())) {
date = date.minusDays(1);
}
}
}
result[i] = date;
}
return result;
}
/**
* Construct an array of dates according the a start date, an end date, the period between dates and the conventions.
* The start date is not included in the array. The date are constructed forward and the stub period, if any, is last.
* The end date is always included in the schedule.
* @param startDate The reference initial date for the construction.
* @param endDate The end date. Usually unadjusted.
* @param period The period between payments.
* @param businessDayConvention The business day convention.
* @param calendar The applicable calendar.
* @param isEOM The end-of-month rule flag.
* @param stubShort Flag indicating if the stub, if any, is short (true) or long (false).
* @return The array of dates.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final ZonedDateTime endDate, final Period period,
final BusinessDayConvention businessDayConvention, final Calendar calendar, final boolean isEOM, final boolean stubShort) {
boolean eomApply = false;
if (isEOM) {
final BusinessDayConvention following = new FollowingBusinessDayConvention();
eomApply = (following.adjustDate(calendar, startDate.plusDays(1)).getMonth() != startDate.getMonth());
}
// When the end-of-month rule applies and the start date is on month-end, the dates are the last business day of the month.
BusinessDayConvention actualBDC;
final List<ZonedDateTime> adjustedDates = new ArrayList<>();
ZonedDateTime date = startDate;
if (eomApply) {
actualBDC = new PrecedingBusinessDayConvention(); //To ensure that the date stays in the current month.
date = date.plus(period).with(TemporalAdjusters.lastDayOfMonth());
while (date.isBefore(endDate)) { // date is strictly before endDate
adjustedDates.add(actualBDC.adjustDate(calendar, date));
date = date.plus(period).with(TemporalAdjusters.lastDayOfMonth());
}
} else {
actualBDC = businessDayConvention;
date = date.plus(period);
while (date.isBefore(endDate)) { // date is strictly before endDate
adjustedDates.add(businessDayConvention.adjustDate(calendar, date));
date = date.plus(period);
}
}
// For long stub the last date before end date, if any, is removed.
if (!stubShort && adjustedDates.size() >= 1) {
adjustedDates.remove(adjustedDates.size() - 1);
}
adjustedDates.add(actualBDC.adjustDate(calendar, endDate)); // the end date
return adjustedDates.toArray(EMPTY_ARRAY);
}
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final ZonedDateTime endDate, final Frequency frequency,
final BusinessDayConvention businessDayConvention, final Calendar calendar, final boolean isEOM) {
PeriodFrequency periodFrequency;
if (frequency instanceof PeriodFrequency) {
periodFrequency = (PeriodFrequency) frequency;
} else if (frequency instanceof SimpleFrequency) {
periodFrequency = ((SimpleFrequency) frequency).toPeriodFrequency();
} else {
throw new IllegalArgumentException("For the moment can only deal with PeriodFrequency and SimpleFrequency");
}
final Period period = periodFrequency.getPeriod();
return getAdjustedDateSchedule(startDate, endDate, period, businessDayConvention, calendar, isEOM, true);
}
/**
* Construct an array of dates according the a start date, an end date, the period between dates and the conventions.
* The start date is not included in the array. The date are constructed forward and the stub period, if any, is last
* and short. The end date is always included in the schedule.
* @param startDate The reference initial date for the construction.
* @param endDate The end date. Usually unadjusted.
* @param period The period between payments.
* @param businessDayConvention The business day convention.
* @param calendar The applicable calendar.
* @param isEOM The end-of-month rule flag.
* @return The array of dates.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final ZonedDateTime endDate, final Period period,
final BusinessDayConvention businessDayConvention, final Calendar calendar, final boolean isEOM) {
return getAdjustedDateSchedule(startDate, endDate, period, businessDayConvention, calendar, isEOM, true);
}
/**
* Construct an array of dates according the a start date, an end date, the period between dates and the conventions.
* The start date is not included in the array. The date are constructed forward and the stub period, if any, is last.
* The end date is always included in the schedule.
* @param startDate The reference initial date for the construction.
* @param tenor The annuity tenor.
* @param period The period between payments.
* @param businessDayConvention The business day convention.
* @param calendar The applicable calendar.
* @param isEOM The end-of-month rule flag.
* @param shortStub Flag indicating if the stub, if any, is short (true) or long (false).
* @return The array of dates.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final Period tenor, final Period period,
final BusinessDayConvention businessDayConvention, final Calendar calendar, final boolean isEOM, final boolean shortStub) {
final ZonedDateTime endDate = startDate.plus(tenor);
return getAdjustedDateSchedule(startDate, endDate, period, businessDayConvention, calendar, isEOM, shortStub);
}
/**
* Construct an array of dates according the a start date, an end date, the period between dates and the conventions.
* The start date is not included in the array. The date are constructed forward and the stub period, if any, is short
* and last. The end date is always included in the schedule.
* @param startDate The reference initial date for the construction.
* @param tenorAnnuity The annuity tenor.
* @param periodPayments The period between payments.
* @param businessDayConvention The business day convention.
* @param calendar The applicable calendar.
* @param isEOM The end-of-month rule flag.
* @return The array of dates.
*/
public static ZonedDateTime[] getAdjustedDateSchedule(final ZonedDateTime startDate, final Period tenorAnnuity, final Period periodPayments,
final BusinessDayConvention businessDayConvention, final Calendar calendar, final boolean isEOM) {
final ZonedDateTime endDate = startDate.plus(tenorAnnuity);
return getAdjustedDateSchedule(startDate, endDate, periodPayments, businessDayConvention, calendar, isEOM, true);
}
public static ZonedDateTime[] getSettlementDateSchedule(final ZonedDateTime[] dates, final Calendar calendar, final BusinessDayConvention businessDayConvention,
final int settlementDays) {
ArgumentChecker.notEmpty(dates, "dates");
ArgumentChecker.notNull(calendar, "calendar");
final int n = dates.length;
final ZonedDateTime[] result = new ZonedDateTime[n];
for (int i = 0; i < n; i++) {
ZonedDateTime date = businessDayConvention.adjustDate(calendar, dates[i].plusDays(1));
for (int j = 0; j < settlementDays; j++) {
date = businessDayConvention.adjustDate(calendar, date.plusDays(1));
}
result[i] = date;
}
return result;
}
public static LocalDate[] getSettlementDateSchedule(final LocalDate[] dates, final Calendar calendar, final BusinessDayConvention businessDayConvention,
final int settlementDays) {
ArgumentChecker.notEmpty(dates, "dates");
ArgumentChecker.notNull(calendar, "calendar");
final int n = dates.length;
final LocalDate[] result = new LocalDate[n];
for (int i = 0; i < n; i++) {
LocalDate date = businessDayConvention.adjustDate(calendar, dates[i].plusDays(1));
for (int j = 0; j < settlementDays; j++) {
date = businessDayConvention.adjustDate(calendar, date.plusDays(1));
}
result[i] = date;
}
return result;
}
public static ZonedDateTime[] getAdjustedResetDateSchedule(final ZonedDateTime effectiveDate, final ZonedDateTime[] dates, final BusinessDayConvention convention,
final Calendar calendar, final int settlementDays) {
ArgumentChecker.notNull(effectiveDate, "effective date");
ArgumentChecker.notEmpty(dates, "dates");
ArgumentChecker.notNull(convention, "convention");
ArgumentChecker.notNull(calendar, "calendar");
final int n = dates.length;
final ZonedDateTime[] result = new ZonedDateTime[n];
result[0] = effectiveDate;
for (int i = 1; i < n; i++) {
result[i] = convention.adjustDate(calendar, dates[i - 1].minusDays(settlementDays));
}
return result;
}
public static ZonedDateTime[] getAdjustedMaturityDateSchedule(final ZonedDateTime effectiveDate, final ZonedDateTime[] dates, final BusinessDayConvention convention,
final Calendar calendar, final Frequency frequency) {
ArgumentChecker.notEmpty(dates, "dates");
ArgumentChecker.notNull(convention, "convention");
ArgumentChecker.notNull(calendar, "calendar");
ArgumentChecker.notNull(frequency, "frequency");
PeriodFrequency periodFrequency;
if (frequency instanceof PeriodFrequency) {
periodFrequency = (PeriodFrequency) frequency;
} else if (frequency instanceof SimpleFrequency) {
periodFrequency = ((SimpleFrequency) frequency).toPeriodFrequency();
} else {
throw new IllegalArgumentException("For the moment can only deal with PeriodFrequency and SimpleFrequency");
}
final Period period = periodFrequency.getPeriod();
final int n = dates.length;
final ZonedDateTime[] results = new ZonedDateTime[n];
results[0] = effectiveDate.plus(period);
for (int i = 1; i < n; i++) {
results[i] = convention.adjustDate(calendar, dates[i - 1].plus(period)); // TODO need to further shift these dates by a convention
}
return results;
}
/**
* Converts a set of dates into time periods in years for a specified date and using a specified day count convention.
*
* @param dates a set of dates, not null
* @param dayCount the day count convention, not null
* @param fromDate the date from which to measure the time period to the dates, not null
* @return a double array of time periods (in years) - if a date is <b>before</b> the fromDate as negative value is returned, not null
*/
public static double[] getTimes(final ZonedDateTime[] dates, final DayCount dayCount, final ZonedDateTime fromDate) {
ArgumentChecker.notEmpty(dates, "dates");
ArgumentChecker.notNull(dayCount, "day count");
ArgumentChecker.notNull(fromDate, "from date");
final int n = dates.length;
final double[] result = new double[n];
double yearFrac;
for (int i = 0; i < (n); i++) {
if (dates[i].isAfter(fromDate)) {
yearFrac = dayCount.getDayCountFraction(fromDate, dates[i]);
} else {
yearFrac = -dayCount.getDayCountFraction(dates[i], fromDate);
}
result[i] = yearFrac;
}
return result;
}
public static int numberOfNegativeValues(final double[] periods) {
int count = 0;
for (final double period : periods) {
if (period < 0.0) {
count++;
}
}
return count;
}
public static double[] removeFirstNValues(final double[] data, final int n) {
return Arrays.copyOfRange(data, n, data.length);
}
public static double[] getYearFractions(final ZonedDateTime[] dates, final DayCount dayCount, final ZonedDateTime fromDate) {
ArgumentChecker.notEmpty(dates, "dates");
ArgumentChecker.notNull(dayCount, "day count");
ArgumentChecker.notNull(fromDate, "from date");
final int n = dates.length;
final double[] result = new double[n];
result[0] = dayCount.getDayCountFraction(fromDate, dates[0]);
for (int i = 1; i < n; i++) {
result[i] = dayCount.getDayCountFraction(dates[i - 1], dates[i]);
}
return result;
}
}