/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.basics.schedule; import java.io.Serializable; import java.time.LocalDate; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import org.joda.beans.Bean; import org.joda.beans.BeanDefinition; import org.joda.beans.ImmutableBean; import org.joda.beans.ImmutableValidator; import org.joda.beans.JodaBeanUtils; import org.joda.beans.MetaProperty; import org.joda.beans.Property; import org.joda.beans.PropertyDefinition; import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; import org.joda.beans.impl.direct.DirectMetaBean; import org.joda.beans.impl.direct.DirectMetaProperty; import org.joda.beans.impl.direct.DirectMetaPropertyMap; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.date.AdjustableDate; import com.opengamma.strata.basics.date.BusinessDayAdjustment; import com.opengamma.strata.collect.ArgChecker; /** * Definition of a periodic schedule. * <p> * A periodic schedule is determined using a "periodic frequency". * This splits the schedule into "regular" periods of a fixed length, such as every 3 months. * Any remaining days are allocated to irregular "stubs" at the start and/or end. * <p> * For example, a 24 month (2 year) swap might be divided into 3 month periods. * The 24 month period is the overall schedule and the 3 month period is the periodic frequency. * <p> * Note that a 23 month swap cannot be split into even 3 month periods. * Instead, there will be a 2 month "initial" stub at the start, a 2 month "final" stub at the end * or both an initial and final stub with a combined length of 2 months. * * <h4>Example</h4> * <p> * This example creates a schedule for a 13 month swap cannot be split into 3 month periods * with a long initial stub rolling at end-of-month: * <pre> * // example swap using builder * BusinessDayAdjustment businessDayAdj = * BusinessDayAdjustment.of(BusinessDayConventions.MODIFIED_FOLLOWING, GlobalHolidayCalendars.EUTA); * PeriodicSchedule definition = PeriodicSchedule.builder() * .startDate(LocalDate.of(2014, 2, 12)) * .endDate(LocalDate.of(2015, 3, 31)) * .businessDayAdjustment(businessDayAdj) * .frequency(Frequency.P3M) * .stubConvention(StubConvention.LONG_INITIAL) * .rollConvention(RollConventions.EOM) * .build(); * Schedule schedule = definition.createSchedule(); * * // result * period 1: 2014-02-12 to 2014-06-30 * period 2: 2014-06-30 to 2014-09-30 * period 3: 2014-09-30 to 2014-12-31 * period 4: 2014-12-31 to 2015-03-31 * </pre> * * <h4>Details about stubs and date rolling</h4> * <p> * The stubs are specified using a combination of the {@link StubConvention}, {@link RollConvention} and dates. * <p> * The explicit stub dates are checked first. An explicit stub occurs if 'firstRegularStartDate' or * 'lastRegularEndDate' is present and they differ from 'startDate' and 'endDate'. * <p> * If explicit stub dates are specified then they are used to lock the initial or final stub. * If the stub convention is present, it is matched and validated against the locked stub. * For example, if an initial stub is specified by dates and the stub convention is 'ShortInitial' * or 'LongInitial' then the convention is considered to be matched, thus the periodic frequency is * applied using the implicit stub convention 'None'. * If the stub convention does not match the dates, then an exception will be thrown during schedule creation. * If the stub convention is not present, then the periodic frequency is applied * using the implicit stub convention 'None'. * <p> * If explicit stub dates are not specified then the stub convention is used. * The convention selects whether to use the start date or the end date as the beginning of the schedule calculation. * The beginning of the calculation must match the roll convention, unless the convention is 'EOM', * in which case 'EOM' is only applied if the calculation starts at the end of the month. * <p> * In all cases, the roll convention is used to fine-tune the dates. * If not present or 'None', the convention is effectively implied from the first date of the calculation. * All calculated dates will match the roll convention. * If this is not possible due to the dates specified then an exception will be thrown during schedule creation. * <p> * It is permitted to have 'firstRegularStartDate' equal to 'endDate', or 'lastRegularEndDate' equal to 'startDate'. * In both cases, the effect is to define a schedule that is entirely "stub" and has no regular periods. * The resulting schedule will retain the frequency specified here, even though it is not used. * <p> * The schedule operates primarily on "unadjusted" dates. * An unadjusted date can be any day, including non-business days. * When the unadjusted schedule has been determined, the appropriate business day adjustment * is applied to create a parallel schedule of "adjusted" dates. */ @BeanDefinition public final class PeriodicSchedule implements ImmutableBean, Serializable { /** * The start date, which is the start of the first schedule period. * <p> * This is the start date of the schedule. * It is is unadjusted and as such might be a weekend or holiday. * Any applicable business day adjustment will be applied when creating the schedule. * This is also known as the unadjusted effective date. * <p> * In most cases, the start date of a financial instrument is just after the trade date, * such as two business days later. However, the start date of a schedule is permitted * to be any date, which includes dates before or after the trade date. */ @PropertyDefinition(validate = "notNull") private final LocalDate startDate; /** * The end date, which is the end of the last schedule period. * <p> * This is the end date of the schedule. * It is is unadjusted and as such might be a weekend or holiday. * Any applicable business day adjustment will be applied when creating the schedule. * This is also known as the unadjusted maturity date or unadjusted termination date. * This date must be after the start date. */ @PropertyDefinition(validate = "notNull") private final LocalDate endDate; /** * The regular periodic frequency to use. * <p> * Most dates are calculated using a regular periodic frequency, such as every 3 months. * The actual day-of-month or day-of-week is selected using the roll and stub conventions. */ @PropertyDefinition(validate = "notNull") private final Frequency frequency; /** * The business day adjustment to apply. * <p> * Each date in the calculated schedule is determined without taking into account weekends and holidays. * The adjustment specified here is used to convert those dates to valid business days. * <p> * The start date and end date may have their own business day adjustment rules. * If those are not present, then this adjustment is used instead. */ @PropertyDefinition(validate = "notNull") private final BusinessDayAdjustment businessDayAdjustment; /** * The optional business day adjustment to apply to the start date. * <p> * The start date property is an unadjusted date and as such might be a weekend or holiday. * The adjustment specified here is used to convert the start date to a valid business day. * <p> * If this property is not present, the standard {@code businessDayAdjustment} property is used instead. */ @PropertyDefinition(get = "optional") private final BusinessDayAdjustment startDateBusinessDayAdjustment; /** * The optional business day adjustment to apply to the end date. * <p> * The end date property is an unadjusted date and as such might be a weekend or holiday. * The adjustment specified here is used to convert the end date to a valid business day. * <p> * If this property is not present, the standard {@code businessDayAdjustment} property is used instead. */ @PropertyDefinition(get = "optional") private final BusinessDayAdjustment endDateBusinessDayAdjustment; /** * The optional convention defining how to handle stubs. * <p> * The stub convention is used during schedule construction to determine whether the irregular * remaining period occurs at the start or end of the schedule. * It also determines whether the irregular period is shorter or longer than the regular period. * This property interacts with the "explicit dates" of {@link PeriodicSchedule#getFirstRegularStartDate()} * and {@link PeriodicSchedule#getLastRegularEndDate()}. * <p> * The convention 'None' may be used to explicitly indicate there are no stubs. * There must be no explicit dates. * This will be validated during schedule construction. * <p> * The convention 'Both' may be used to explicitly indicate there is both an initial and final stub. * The stubs themselves must be specified using explicit dates. * This will be validated during schedule construction. * <p> * The conventions 'ShortInitial', 'LongInitial', 'ShortFinal' and 'LongFinal' are used to * indicate the type of stub to be generated. The exact behavior varies depending on whether * there are explicit dates or not: * <p> * If explicit dates are specified, then the combination of stub convention an explicit date * will be validated during schedule construction. For example, the combination of an explicit dated * initial stub and a stub convention of 'ShortInitial' or 'LongInitial' is valid, but other * stub conventions, such as 'ShortFinal' or 'None' would be invalid. * <p> * If explicit dates are not specified, then it is not required that a stub is generated. * The convention determines whether to generate dates from the start date forward, or the * end date backwards. Date generation may or may not result in a stub, but if it does then * the stub will be of the correct type. * <p> * When the stub convention is not present, the generation of stubs is based entirely on * the presence or absence of the explicit dates. */ @PropertyDefinition(get = "optional") private final StubConvention stubConvention; /** * The optional convention defining how to roll dates. * <p> * The schedule periods are determined at the high level by repeatedly adding * the frequency to the start date, or subtracting it from the end date. * The roll convention provides the detailed rule to adjust the day-of-month or day-of-week. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the roll convention will be implied. */ @PropertyDefinition(get = "optional") private final RollConvention rollConvention; /** * The optional start date of the first regular schedule period, which is the end date of the initial stub. * <p> * This is used to identify the boundary date between the initial stub and the first regular schedule period. * <p> * This is an unadjusted date, and as such it might not be a valid business day. * This date must be on or after 'startDate'. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the overall schedule start date will be used instead, resulting in no initial stub. */ @PropertyDefinition(get = "optional") private final LocalDate firstRegularStartDate; /** * The optional end date of the last regular schedule period, which is the start date of the final stub. * <p> * This is used to identify the boundary date between the last regular schedule period and the final stub. * <p> * This is an unadjusted date, and as such it might not be a valid business day. * This date must be after 'startDate' and after 'firstRegularStartDate'. * This date must be on or before 'endDate'. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the overall schedule end date will be used instead, resulting in no final stub. */ @PropertyDefinition(get = "optional") private final LocalDate lastRegularEndDate; /** * The optional start date of the first schedule period, overriding normal schedule generation. * <p> * This property is rarely used, and is generally needed when accrual starts before the effective date. * If specified, it overrides the start date of the first period once schedule generation has been completed. * Note that all schedule generation rules apply to 'startDate', with this applied as a final step. * This field primarily exists to support the FpML 'firstPeriodStartDate' concept. * <p> * If set, it should be different to the start date, although this is not validated. * Validation does check that it is before the 'firstRegularStartDate'. * <p> * During schedule generation, if this is present it will be used to override the start date * of the first generated schedule period. * If not present, then the start of the first period will be the normal start date. */ @PropertyDefinition(get = "optional") private final AdjustableDate overrideStartDate; //------------------------------------------------------------------------- /** * Obtains an instance based on a stub convention and end-of-month flag. * <p> * The business day adjustment is used for all dates. * The stub convention is used to determine whether there are any stubs. * If the end-of-month flag is true, then in any case of ambiguity the * end-of-month will be chosen. * * @param unadjustedStartDate start date, which is the start of the first schedule period * @param unadjustedEndDate the end date, which is the end of the last schedule period * @param frequency the regular periodic frequency * @param businessDayAdjustment the business day adjustment to apply * @param stubConvention the non-null convention defining how to handle stubs * @param preferEndOfMonth whether to prefer the end-of-month when rolling * @return the definition */ public static PeriodicSchedule of( LocalDate unadjustedStartDate, LocalDate unadjustedEndDate, Frequency frequency, BusinessDayAdjustment businessDayAdjustment, StubConvention stubConvention, boolean preferEndOfMonth) { ArgChecker.notNull(unadjustedStartDate, "unadjustedStartDate"); ArgChecker.notNull(unadjustedEndDate, "unadjustedEndDate"); ArgChecker.notNull(frequency, "frequency"); ArgChecker.notNull(businessDayAdjustment, "businessDayAdjustment"); ArgChecker.notNull(stubConvention, "stubConvention"); return PeriodicSchedule.builder() .startDate(unadjustedStartDate) .endDate(unadjustedEndDate) .frequency(frequency) .businessDayAdjustment(businessDayAdjustment) .stubConvention(stubConvention) .rollConvention(preferEndOfMonth ? RollConventions.EOM : null) .build(); } /** * Obtains an instance based on roll and stub conventions. * <p> * The business day adjustment is used for all dates. * The stub convention is used to determine whether there are any stubs. * The roll convention is used to fine tune each rolled date. * * @param unadjustedStartDate start date, which is the start of the first schedule period * @param unadjustedEndDate the end date, which is the end of the last schedule period * @param frequency the regular periodic frequency * @param businessDayAdjustment the business day adjustment to apply * @param stubConvention the non-null convention defining how to handle stubs * @param rollConvention the non-null convention defining how to roll dates * @return the definition */ public static PeriodicSchedule of( LocalDate unadjustedStartDate, LocalDate unadjustedEndDate, Frequency frequency, BusinessDayAdjustment businessDayAdjustment, StubConvention stubConvention, RollConvention rollConvention) { ArgChecker.notNull(unadjustedStartDate, "unadjustedStartDate"); ArgChecker.notNull(unadjustedEndDate, "unadjustedEndDate"); ArgChecker.notNull(frequency, "frequency"); ArgChecker.notNull(businessDayAdjustment, "businessDayAdjustment"); ArgChecker.notNull(stubConvention, "stubConvention"); ArgChecker.notNull(rollConvention, "rollConvention"); return PeriodicSchedule.builder() .startDate(unadjustedStartDate) .endDate(unadjustedEndDate) .frequency(frequency) .businessDayAdjustment(businessDayAdjustment) .stubConvention(stubConvention) .rollConvention(rollConvention) .build(); } //------------------------------------------------------------------------- @ImmutableValidator private void validate() { ArgChecker.inOrderNotEqual( startDate, endDate, "startDate", "endDate"); if (firstRegularStartDate != null) { ArgChecker.inOrderOrEqual( startDate, firstRegularStartDate, "unadjusted", "firstRegularStartDate"); if (lastRegularEndDate != null) { ArgChecker.inOrderNotEqual( firstRegularStartDate, lastRegularEndDate, "firstRegularStartDate", "lastRegularEndDate"); } if (overrideStartDate != null) { ArgChecker.inOrderNotEqual( overrideStartDate.getUnadjusted(), firstRegularStartDate, "overrideStartDate", "firstRegularStartDate"); } } if (lastRegularEndDate != null) { ArgChecker.inOrderOrEqual( lastRegularEndDate, endDate, "lastRegularEndDate", "endDate"); } } //------------------------------------------------------------------------- /** * Creates the schedule from the definition. * <p> * The schedule consists of an optional initial stub, a number of regular periods * and an optional final stub. * <p> * The roll convention, stub convention and additional dates are all used to determine the schedule. * If the roll convention is not present it will be defaulted from the stub convention, with 'None' as the default. * If there are explicit stub dates then they will be used. * If the stub convention is present, then it will be validated against the stub dates. * If the stub convention and stub dates are not present, then no stubs are allowed. * <p> * There is special handling for pre-adjusted start dates to avoid creating incorrect stubs. * If all the following conditions hold true, then the unadjusted start date is treated * as being the day-of-month implied by the roll convention (the adjusted date is unaffected). * <ul> * <li>the {@code startDateBusinessDayAdjustment} property equals {@link BusinessDayAdjustment#NONE} * <li>the {@code firstRegularStartDate} property is null * <li>the roll convention is numeric or 'EOM' * <li>applying {@code businessDayAdjustment} to the day-of-month implied by the roll convention * yields the specified start date * </ul> * * @return the schedule * @param refData the reference data, used to find the holiday calendars * @throws ScheduleException if the definition is invalid */ public Schedule createSchedule(ReferenceData refData) { LocalDate unadjStart = calculatedUnadjustedStartDate(refData); LocalDate regularStart = firstRegularStartDate != null ? firstRegularStartDate : unadjStart; RollConvention rollConv = calculatedRollConvention(regularStart); List<LocalDate> unadj = generateUnadjustedDates(unadjStart, regularStart, rollConv); List<LocalDate> adj = applyBusinessDayAdjustment(unadj, refData); List<SchedulePeriod> periods = new ArrayList<>(); try { // for performance, handle silly errors using exceptions for (int i = 0; i < unadj.size() - 1; i++) { periods.add(SchedulePeriod.of(adj.get(i), adj.get(i + 1), unadj.get(i), unadj.get(i + 1))); } } catch (IllegalArgumentException ex) { // check dates to throw a better exception for duplicate dates in schedule createUnadjustedDates(); createAdjustedDates(refData); // unknown exception ScheduleException se = new ScheduleException(this, "Schedule calculation resulted in invalid period"); se.initCause(ex); throw se; } return Schedule.builder() .periods(periods) .frequency(frequency) .rollConvention(rollConv) .build(); } //------------------------------------------------------------------------- /** * Creates the list of unadjusted dates in the schedule. * <p> * The unadjusted date list will contain at least two elements, the start date and end date. * Between those dates will be the calculated periodic schedule. * <p> * The roll convention, stub convention and additional dates are all used to determine the schedule. * If the roll convention is not present it will be defaulted from the stub convention, with 'None' as the default. * If there are explicit stub dates then they will be used. * If the stub convention is present, then it will be validated against the stub dates. * If the stub convention and stub dates are not present, then no stubs are allowed. * If the frequency is 'Term' explicit stub dates are disallowed, and the roll and stub convention are ignored. * <p> * The special handling for last business day of month seen in * {@link #createUnadjustedDates(ReferenceData)} is not applied. * * @return the schedule of unadjusted dates * @throws ScheduleException if the definition is invalid */ public ImmutableList<LocalDate> createUnadjustedDates() { LocalDate regularStart = calculatedFirstRegularStartDate(); RollConvention rollConv = calculatedRollConvention(regularStart); List<LocalDate> unadj = generateUnadjustedDates(startDate, regularStart, rollConv); // ensure schedule is valid with no duplicated dates ImmutableList<LocalDate> deduplicated = ImmutableSet.copyOf(unadj).asList(); if (deduplicated.size() < unadj.size()) { throw new ScheduleException(this, "Schedule calculation resulted in duplicate unadjusted dates {}", unadj); } return deduplicated; } /** * Creates the list of unadjusted dates in the schedule. * <p> * The unadjusted date list will contain at least two elements, the start date and end date. * Between those dates will be the calculated periodic schedule. * <p> * The roll convention, stub convention and additional dates are all used to determine the schedule. * If the roll convention is not present it will be defaulted from the stub convention, with 'None' as the default. * If there are explicit stub dates then they will be used. * If the stub convention is present, then it will be validated against the stub dates. * If the stub convention and stub dates are not present, then no stubs are allowed. * If the frequency is 'Term' explicit stub dates are disallowed, and the roll and stub convention are ignored. * <p> * There is special handling for pre-adjusted start dates to avoid creating incorrect stubs. * If all the following conditions hold true, then the unadjusted start date is treated * as being the day-of-month implied by the roll convention (the adjusted date is unaffected). * <ul> * <li>the {@code startDateBusinessDayAdjustment} property equals {@link BusinessDayAdjustment#NONE} * <li>the {@code firstRegularStartDate} property is null * <li>the roll convention is numeric or 'EOM' * <li>applying {@code businessDayAdjustment} to the day-of-month implied by the roll convention * yields the specified start date * </ul> * * @param refData the reference data, used to find the holiday calendars * @return the schedule of unadjusted dates * @throws ScheduleException if the definition is invalid */ public ImmutableList<LocalDate> createUnadjustedDates(ReferenceData refData) { LocalDate unadjStart = calculatedUnadjustedStartDate(refData); LocalDate regularStart = firstRegularStartDate != null ? firstRegularStartDate : unadjStart; RollConvention rollConv = calculatedRollConvention(regularStart); List<LocalDate> unadj = generateUnadjustedDates(unadjStart, regularStart, rollConv); // ensure schedule is valid with no duplicated dates ImmutableList<LocalDate> deduplicated = ImmutableSet.copyOf(unadj).asList(); if (deduplicated.size() < unadj.size()) { throw new ScheduleException(this, "Schedule calculation resulted in duplicate unadjusted dates {}", unadj); } return deduplicated; } // creates the unadjusted dates, returning the mutable list private List<LocalDate> generateUnadjustedDates( LocalDate unadjStartDate, LocalDate regStart, RollConvention rollConv) { LocalDate overriddenStartDate = overrideStartDate != null ? overrideStartDate.getUnadjusted() : unadjStartDate; LocalDate regEnd = calculatedLastRegularEndDate(); boolean explicitInitialStub = !unadjStartDate.equals(regStart); boolean explicitFinalStub = !endDate.equals(regEnd); // handle case where whole period is stub if (regStart.equals(endDate) || regEnd.equals(unadjStartDate)) { return ImmutableList.of(overriddenStartDate, endDate); } // handle TERM frequency if (frequency == Frequency.TERM) { if (explicitInitialStub || explicitFinalStub) { throw new ScheduleException(this, "Explict stubs must not be specified when using 'Term' frequency"); } return ImmutableList.of(overriddenStartDate, endDate); } // calculate base schedule excluding explicit stubs StubConvention stubConv = generateImplicitStubConvention(explicitInitialStub, explicitFinalStub); return (stubConv.isCalculateBackwards() ? generateBackwards( regStart, regEnd, rollConv, stubConv, explicitInitialStub, overriddenStartDate, explicitFinalStub, endDate) : generateForwards( regStart, regEnd, rollConv, stubConv, explicitInitialStub, overriddenStartDate, explicitFinalStub, endDate)); } // using knowledge of the explicit stubs, generate the correct convention for implicit stubs private StubConvention generateImplicitStubConvention(boolean explicitInitialStub, boolean explicitFinalStub) { // null is not same as NONE // NONE validates that there are no explicit stubs // null ensures that remainder after explicit stubs are removed has no stubs if (stubConvention != null) { return stubConvention.toImplicit(this, explicitInitialStub, explicitFinalStub); } return StubConvention.NONE; } // generate the schedule of dates backwards from the end private List<LocalDate> generateBackwards( LocalDate start, LocalDate end, RollConvention rollConv, StubConvention stubConv, boolean explicitInitialStub, LocalDate explicitStartDate, boolean explicitFinalStub, LocalDate explicitEndDate) { // validate if (rollConv.matches(end) == false) { throw new ScheduleException( this, "Date '{}' does not match roll convention '{}' when starting to roll backwards", end, rollConv); } // generate BackwardsList dates = new BackwardsList(estimateNumberPeriods(start, end)); if (explicitFinalStub) { dates.addFirst(explicitEndDate); } dates.addFirst(end); LocalDate temp = rollConv.previous(end, frequency); while (temp.isAfter(start)) { dates.addFirst(temp); temp = rollConv.previous(temp, frequency); } // convert short stub to long stub, but only if we actually have a stub boolean stub = temp.equals(start) == false; if (stub && stubConv.isLong() && dates.size() > 1) { dates.removeFirst(); } if (explicitInitialStub) { dates.addFirst(start); dates.addFirst(explicitStartDate); } else { dates.addFirst(explicitStartDate); } return dates; } // dedicated list implementation for backwards looping for performance // only implements those methods that are needed private static class BackwardsList extends AbstractList<LocalDate> { private int first; private LocalDate[] array; BackwardsList(int capacity) { this.array = new LocalDate[capacity]; this.first = array.length; } @Override public LocalDate get(int index) { return array[first + index]; } @Override public int size() { return array.length - first; } void addFirst(LocalDate date) { array[--first] = date; } void removeFirst() { first++; } } // generate the schedule of dates forwards from the start private List<LocalDate> generateForwards( LocalDate start, LocalDate end, RollConvention rollConv, StubConvention stubConv, boolean explicitInitialStub, LocalDate explicitStartDate, boolean explicitFinalStub, LocalDate explicitEndDate) { // validate if (rollConv.matches(start) == false) { throw new ScheduleException( this, "Date '{}' does not match roll convention '{}' when starting to roll forwards", start, rollConv); } // generate List<LocalDate> dates = new ArrayList<>(estimateNumberPeriods(start, end)); if (explicitInitialStub) { dates.add(explicitStartDate); dates.add(start); } else { dates.add(explicitStartDate); } LocalDate temp = rollConv.next(start, frequency); while (temp.isBefore(end)) { dates.add(temp); temp = rollConv.next(temp, frequency); } // convert short stub to long stub, but only if we actually have a stub boolean stub = temp.equals(end) == false; if (stub && dates.size() > 1) { if (stubConv == StubConvention.NONE) { throw new ScheduleException( this, "Period '{}' to '{}' resulted in a disallowed stub with frequency '{}'", start, end, frequency); } if (stubConv.isLong()) { dates.remove(dates.size() - 1); } } dates.add(end); if (explicitFinalStub) { dates.add(explicitEndDate); } return dates; } // roughly estimate the number of periods (overestimating) private int estimateNumberPeriods(LocalDate start, LocalDate end) { int termInYearsEstimate = end.getYear() - start.getYear() + 2; return (int) (Math.max(frequency.eventsPerYearEstimate(), 1) * termInYearsEstimate); } //------------------------------------------------------------------------- /** * Creates the list of adjusted dates in the schedule. * <p> * The adjusted date list will contain at least two elements, the start date and end date. * Between those dates will be the calculated periodic schedule. * Each date will be a valid business day as per the appropriate business day adjustment. * <p> * The roll convention, stub convention and additional dates are all used to determine the schedule. * If the roll convention is not present it will be defaulted from the stub convention, with 'None' as the default. * If there are explicit stub dates then they will be used. * If the stub convention is present, then it will be validated against the stub dates. * If the stub convention and stub dates are not present, then no stubs are allowed. * <p> * There is special handling for pre-adjusted start dates to avoid creating incorrect stubs. * If all the following conditions hold true, then the unadjusted start date is treated * as being the day-of-month implied by the roll convention (the adjusted date is unaffected). * <ul> * <li>the {@code startDateBusinessDayAdjustment} property equals {@link BusinessDayAdjustment#NONE} * <li>the {@code firstRegularStartDate} property is null * <li>the roll convention is numeric or 'EOM' * <li>applying {@code businessDayAdjustment} to the day-of-month implied by the roll convention * yields the specified start date * </ul> * * @return the schedule of dates adjusted to valid business days * @param refData the reference data, used to find the holiday calendar * @throws ScheduleException if the definition is invalid */ public ImmutableList<LocalDate> createAdjustedDates(ReferenceData refData) { LocalDate unadjStart = calculatedUnadjustedStartDate(refData); LocalDate regularStart = firstRegularStartDate != null ? firstRegularStartDate : unadjStart; RollConvention rollConv = calculatedRollConvention(regularStart); List<LocalDate> unadj = generateUnadjustedDates(unadjStart, regularStart, rollConv); List<LocalDate> adj = applyBusinessDayAdjustment(unadj, refData); // ensure schedule is valid with no duplicated dates ImmutableList<LocalDate> deduplicated = ImmutableSet.copyOf(adj).asList(); if (deduplicated.size() < adj.size()) { throw new ScheduleException( this, "Schedule calculation resulted in duplicate adjusted dates {} from unadjusted dates {} using adjustment '{}'", adj, unadj, businessDayAdjustment); } return deduplicated; } // applies the appropriate business day adjustment to each date private List<LocalDate> applyBusinessDayAdjustment(List<LocalDate> unadj, ReferenceData refData) { List<LocalDate> adj = new ArrayList<>(unadj.size()); adj.add(calculatedStartDate().adjusted(refData)); for (int i = 1; i < unadj.size() - 1; i++) { adj.add(businessDayAdjustment.adjust(unadj.get(i), refData)); } adj.add(calculatedEndDate().adjusted(refData)); return adj; } //------------------------------------------------------------------------- /** * Gets the applicable roll convention defining how to roll dates. * <p> * The schedule periods are determined at the high level by repeatedly adding * the frequency to the start date, or subtracting it from the end date. * The roll convention provides the detailed rule to adjust the day-of-month or day-of-week. * <p> * The applicable roll convention is a non-null value. * If the roll convention property is not present, this is determined from the * stub convention, dates and frequency, defaulting to 'None' if necessary. * * @return the non-null roll convention */ public RollConvention calculatedRollConvention() { return calculatedRollConvention(calculatedFirstRegularStartDate()); } // calculates the applicable roll convention // the calculated start date parameter allows for influence by calculatedUnadjustedStartDate() private RollConvention calculatedRollConvention(LocalDate calculatedFirstRegularStartDate) { // determine roll convention from stub convention StubConvention stubConv = MoreObjects.firstNonNull(stubConvention, StubConvention.NONE); // special handling for EOM as it is advisory rather than mandatory if (rollConvention == RollConventions.EOM) { RollConvention derived = stubConv.toRollConvention( calculatedFirstRegularStartDate, calculatedLastRegularEndDate(), frequency, true); return (derived == RollConventions.NONE ? RollConventions.EOM : derived); } // avoid RollConventions.NONE if possible if (rollConvention == null || rollConvention == RollConventions.NONE) { return stubConv.toRollConvention( calculatedFirstRegularStartDate, calculatedLastRegularEndDate(), frequency, false); } // use RollConventions.NONE if nothing else applies return MoreObjects.firstNonNull(rollConvention, RollConventions.NONE); } /** * Calculates the applicable first regular start date. * <p> * This will be either 'firstRegularStartDate' or 'startDate'. * * @return the non-null start date of the first regular period */ public LocalDate calculatedFirstRegularStartDate() { return MoreObjects.firstNonNull(firstRegularStartDate, startDate); } // calculates the applicable start date // applies de facto rule where EOM means last business day for startDate // and similar rule for numeric roll conventions // http://www.fpml.org/forums/topic/can-a-roll-convention-imply-a-stub/#post-7659 private LocalDate calculatedUnadjustedStartDate(ReferenceData refData) { // only allow when firstRegularStartDate not used and start date adjustment is NONE if (rollConvention != null && firstRegularStartDate == null && refData != null && BusinessDayAdjustment.NONE.equals(startDateBusinessDayAdjustment)) { int rollDom = rollConvention.getDayOfMonth(); if (rollDom > 0) { return calculatedUnadjustedStartDate(rollDom, refData); } } return startDate; } // calculates the applicable start date based on the roll day-of-month private LocalDate calculatedUnadjustedStartDate(int rollDom, ReferenceData refData) { int lengthOfMonth = startDate.lengthOfMonth(); int actualDom = Math.min(rollDom, lengthOfMonth); // startDate is already the expected day, then nothing to do if (startDate.getDayOfMonth() != actualDom) { LocalDate rollImpliedDate = startDate.withDayOfMonth(actualDom); LocalDate adjDate = businessDayAdjustment.adjust(rollImpliedDate, refData); if (adjDate.equals(startDate)) { return rollImpliedDate; } } return startDate; } /** * Calculates the applicable last regular end date. * <p> * This will be either 'lastRegularEndDate' or 'endDate'. * * @return the non-null end date of the last regular period */ public LocalDate calculatedLastRegularEndDate() { return MoreObjects.firstNonNull(lastRegularEndDate, endDate); } /** * Calculates the applicable business day adjustment to apply to the start date. * <p> * This will be either 'startDateBusinessDayAdjustment' or 'businessDayAdjustment'. * * @return the non-null business day adjustment to apply to the start date */ private BusinessDayAdjustment calculatedStartDateBusinessDayAdjustment() { return MoreObjects.firstNonNull(startDateBusinessDayAdjustment, businessDayAdjustment); } /** * Calculates the applicable business day adjustment to apply to the end date. * <p> * This will be either 'endDateBusinessDayAdjustment' or 'businessDayAdjustment'. * * @return the non-null business day adjustment to apply to the end date */ private BusinessDayAdjustment calculatedEndDateBusinessDayAdjustment() { return MoreObjects.firstNonNull(endDateBusinessDayAdjustment, businessDayAdjustment); } //------------------------------------------------------------------------- /** * Calculates the applicable start date. * <p> * The result combines the start date and the appropriate business day adjustment. * If the override start date is present, it will be returned. * * @return the calculated start date */ public AdjustableDate calculatedStartDate() { if (overrideStartDate != null) { return overrideStartDate; } return AdjustableDate.of(startDate, calculatedStartDateBusinessDayAdjustment()); } /** * Calculates the applicable end date. * <p> * The result combines the end date and the appropriate business day adjustment. * * @return the calculated end date */ public AdjustableDate calculatedEndDate() { return AdjustableDate.of(endDate, calculatedEndDateBusinessDayAdjustment()); } //------------------------- AUTOGENERATED START ------------------------- ///CLOVER:OFF /** * The meta-bean for {@code PeriodicSchedule}. * @return the meta-bean, not null */ public static PeriodicSchedule.Meta meta() { return PeriodicSchedule.Meta.INSTANCE; } static { JodaBeanUtils.registerMetaBean(PeriodicSchedule.Meta.INSTANCE); } /** * The serialization version id. */ private static final long serialVersionUID = 1L; /** * Returns a builder used to create an instance of the bean. * @return the builder, not null */ public static PeriodicSchedule.Builder builder() { return new PeriodicSchedule.Builder(); } private PeriodicSchedule( LocalDate startDate, LocalDate endDate, Frequency frequency, BusinessDayAdjustment businessDayAdjustment, BusinessDayAdjustment startDateBusinessDayAdjustment, BusinessDayAdjustment endDateBusinessDayAdjustment, StubConvention stubConvention, RollConvention rollConvention, LocalDate firstRegularStartDate, LocalDate lastRegularEndDate, AdjustableDate overrideStartDate) { JodaBeanUtils.notNull(startDate, "startDate"); JodaBeanUtils.notNull(endDate, "endDate"); JodaBeanUtils.notNull(frequency, "frequency"); JodaBeanUtils.notNull(businessDayAdjustment, "businessDayAdjustment"); this.startDate = startDate; this.endDate = endDate; this.frequency = frequency; this.businessDayAdjustment = businessDayAdjustment; this.startDateBusinessDayAdjustment = startDateBusinessDayAdjustment; this.endDateBusinessDayAdjustment = endDateBusinessDayAdjustment; this.stubConvention = stubConvention; this.rollConvention = rollConvention; this.firstRegularStartDate = firstRegularStartDate; this.lastRegularEndDate = lastRegularEndDate; this.overrideStartDate = overrideStartDate; validate(); } @Override public PeriodicSchedule.Meta metaBean() { return PeriodicSchedule.Meta.INSTANCE; } @Override public <R> Property<R> property(String propertyName) { return metaBean().<R>metaProperty(propertyName).createProperty(this); } @Override public Set<String> propertyNames() { return metaBean().metaPropertyMap().keySet(); } //----------------------------------------------------------------------- /** * Gets the start date, which is the start of the first schedule period. * <p> * This is the start date of the schedule. * It is is unadjusted and as such might be a weekend or holiday. * Any applicable business day adjustment will be applied when creating the schedule. * This is also known as the unadjusted effective date. * <p> * In most cases, the start date of a financial instrument is just after the trade date, * such as two business days later. However, the start date of a schedule is permitted * to be any date, which includes dates before or after the trade date. * @return the value of the property, not null */ public LocalDate getStartDate() { return startDate; } //----------------------------------------------------------------------- /** * Gets the end date, which is the end of the last schedule period. * <p> * This is the end date of the schedule. * It is is unadjusted and as such might be a weekend or holiday. * Any applicable business day adjustment will be applied when creating the schedule. * This is also known as the unadjusted maturity date or unadjusted termination date. * This date must be after the start date. * @return the value of the property, not null */ public LocalDate getEndDate() { return endDate; } //----------------------------------------------------------------------- /** * Gets the regular periodic frequency to use. * <p> * Most dates are calculated using a regular periodic frequency, such as every 3 months. * The actual day-of-month or day-of-week is selected using the roll and stub conventions. * @return the value of the property, not null */ public Frequency getFrequency() { return frequency; } //----------------------------------------------------------------------- /** * Gets the business day adjustment to apply. * <p> * Each date in the calculated schedule is determined without taking into account weekends and holidays. * The adjustment specified here is used to convert those dates to valid business days. * <p> * The start date and end date may have their own business day adjustment rules. * If those are not present, then this adjustment is used instead. * @return the value of the property, not null */ public BusinessDayAdjustment getBusinessDayAdjustment() { return businessDayAdjustment; } //----------------------------------------------------------------------- /** * Gets the optional business day adjustment to apply to the start date. * <p> * The start date property is an unadjusted date and as such might be a weekend or holiday. * The adjustment specified here is used to convert the start date to a valid business day. * <p> * If this property is not present, the standard {@code businessDayAdjustment} property is used instead. * @return the optional value of the property, not null */ public Optional<BusinessDayAdjustment> getStartDateBusinessDayAdjustment() { return Optional.ofNullable(startDateBusinessDayAdjustment); } //----------------------------------------------------------------------- /** * Gets the optional business day adjustment to apply to the end date. * <p> * The end date property is an unadjusted date and as such might be a weekend or holiday. * The adjustment specified here is used to convert the end date to a valid business day. * <p> * If this property is not present, the standard {@code businessDayAdjustment} property is used instead. * @return the optional value of the property, not null */ public Optional<BusinessDayAdjustment> getEndDateBusinessDayAdjustment() { return Optional.ofNullable(endDateBusinessDayAdjustment); } //----------------------------------------------------------------------- /** * Gets the optional convention defining how to handle stubs. * <p> * The stub convention is used during schedule construction to determine whether the irregular * remaining period occurs at the start or end of the schedule. * It also determines whether the irregular period is shorter or longer than the regular period. * This property interacts with the "explicit dates" of {@link PeriodicSchedule#getFirstRegularStartDate()} * and {@link PeriodicSchedule#getLastRegularEndDate()}. * <p> * The convention 'None' may be used to explicitly indicate there are no stubs. * There must be no explicit dates. * This will be validated during schedule construction. * <p> * The convention 'Both' may be used to explicitly indicate there is both an initial and final stub. * The stubs themselves must be specified using explicit dates. * This will be validated during schedule construction. * <p> * The conventions 'ShortInitial', 'LongInitial', 'ShortFinal' and 'LongFinal' are used to * indicate the type of stub to be generated. The exact behavior varies depending on whether * there are explicit dates or not: * <p> * If explicit dates are specified, then the combination of stub convention an explicit date * will be validated during schedule construction. For example, the combination of an explicit dated * initial stub and a stub convention of 'ShortInitial' or 'LongInitial' is valid, but other * stub conventions, such as 'ShortFinal' or 'None' would be invalid. * <p> * If explicit dates are not specified, then it is not required that a stub is generated. * The convention determines whether to generate dates from the start date forward, or the * end date backwards. Date generation may or may not result in a stub, but if it does then * the stub will be of the correct type. * <p> * When the stub convention is not present, the generation of stubs is based entirely on * the presence or absence of the explicit dates. * @return the optional value of the property, not null */ public Optional<StubConvention> getStubConvention() { return Optional.ofNullable(stubConvention); } //----------------------------------------------------------------------- /** * Gets the optional convention defining how to roll dates. * <p> * The schedule periods are determined at the high level by repeatedly adding * the frequency to the start date, or subtracting it from the end date. * The roll convention provides the detailed rule to adjust the day-of-month or day-of-week. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the roll convention will be implied. * @return the optional value of the property, not null */ public Optional<RollConvention> getRollConvention() { return Optional.ofNullable(rollConvention); } //----------------------------------------------------------------------- /** * Gets the optional start date of the first regular schedule period, which is the end date of the initial stub. * <p> * This is used to identify the boundary date between the initial stub and the first regular schedule period. * <p> * This is an unadjusted date, and as such it might not be a valid business day. * This date must be on or after 'startDate'. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the overall schedule start date will be used instead, resulting in no initial stub. * @return the optional value of the property, not null */ public Optional<LocalDate> getFirstRegularStartDate() { return Optional.ofNullable(firstRegularStartDate); } //----------------------------------------------------------------------- /** * Gets the optional end date of the last regular schedule period, which is the start date of the final stub. * <p> * This is used to identify the boundary date between the last regular schedule period and the final stub. * <p> * This is an unadjusted date, and as such it might not be a valid business day. * This date must be after 'startDate' and after 'firstRegularStartDate'. * This date must be on or before 'endDate'. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the overall schedule end date will be used instead, resulting in no final stub. * @return the optional value of the property, not null */ public Optional<LocalDate> getLastRegularEndDate() { return Optional.ofNullable(lastRegularEndDate); } //----------------------------------------------------------------------- /** * Gets the optional start date of the first schedule period, overriding normal schedule generation. * <p> * This property is rarely used, and is generally needed when accrual starts before the effective date. * If specified, it overrides the start date of the first period once schedule generation has been completed. * Note that all schedule generation rules apply to 'startDate', with this applied as a final step. * This field primarily exists to support the FpML 'firstPeriodStartDate' concept. * <p> * If set, it should be different to the start date, although this is not validated. * Validation does check that it is before the 'firstRegularStartDate'. * <p> * During schedule generation, if this is present it will be used to override the start date * of the first generated schedule period. * If not present, then the start of the first period will be the normal start date. * @return the optional value of the property, not null */ public Optional<AdjustableDate> getOverrideStartDate() { return Optional.ofNullable(overrideStartDate); } //----------------------------------------------------------------------- /** * Returns a builder that allows this bean to be mutated. * @return the mutable builder, not null */ public Builder toBuilder() { return new Builder(this); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj.getClass() == this.getClass()) { PeriodicSchedule other = (PeriodicSchedule) obj; return JodaBeanUtils.equal(startDate, other.startDate) && JodaBeanUtils.equal(endDate, other.endDate) && JodaBeanUtils.equal(frequency, other.frequency) && JodaBeanUtils.equal(businessDayAdjustment, other.businessDayAdjustment) && JodaBeanUtils.equal(startDateBusinessDayAdjustment, other.startDateBusinessDayAdjustment) && JodaBeanUtils.equal(endDateBusinessDayAdjustment, other.endDateBusinessDayAdjustment) && JodaBeanUtils.equal(stubConvention, other.stubConvention) && JodaBeanUtils.equal(rollConvention, other.rollConvention) && JodaBeanUtils.equal(firstRegularStartDate, other.firstRegularStartDate) && JodaBeanUtils.equal(lastRegularEndDate, other.lastRegularEndDate) && JodaBeanUtils.equal(overrideStartDate, other.overrideStartDate); } return false; } @Override public int hashCode() { int hash = getClass().hashCode(); hash = hash * 31 + JodaBeanUtils.hashCode(startDate); hash = hash * 31 + JodaBeanUtils.hashCode(endDate); hash = hash * 31 + JodaBeanUtils.hashCode(frequency); hash = hash * 31 + JodaBeanUtils.hashCode(businessDayAdjustment); hash = hash * 31 + JodaBeanUtils.hashCode(startDateBusinessDayAdjustment); hash = hash * 31 + JodaBeanUtils.hashCode(endDateBusinessDayAdjustment); hash = hash * 31 + JodaBeanUtils.hashCode(stubConvention); hash = hash * 31 + JodaBeanUtils.hashCode(rollConvention); hash = hash * 31 + JodaBeanUtils.hashCode(firstRegularStartDate); hash = hash * 31 + JodaBeanUtils.hashCode(lastRegularEndDate); hash = hash * 31 + JodaBeanUtils.hashCode(overrideStartDate); return hash; } @Override public String toString() { StringBuilder buf = new StringBuilder(384); buf.append("PeriodicSchedule{"); buf.append("startDate").append('=').append(startDate).append(',').append(' '); buf.append("endDate").append('=').append(endDate).append(',').append(' '); buf.append("frequency").append('=').append(frequency).append(',').append(' '); buf.append("businessDayAdjustment").append('=').append(businessDayAdjustment).append(',').append(' '); buf.append("startDateBusinessDayAdjustment").append('=').append(startDateBusinessDayAdjustment).append(',').append(' '); buf.append("endDateBusinessDayAdjustment").append('=').append(endDateBusinessDayAdjustment).append(',').append(' '); buf.append("stubConvention").append('=').append(stubConvention).append(',').append(' '); buf.append("rollConvention").append('=').append(rollConvention).append(',').append(' '); buf.append("firstRegularStartDate").append('=').append(firstRegularStartDate).append(',').append(' '); buf.append("lastRegularEndDate").append('=').append(lastRegularEndDate).append(',').append(' '); buf.append("overrideStartDate").append('=').append(JodaBeanUtils.toString(overrideStartDate)); buf.append('}'); return buf.toString(); } //----------------------------------------------------------------------- /** * The meta-bean for {@code PeriodicSchedule}. */ public static final class Meta extends DirectMetaBean { /** * The singleton instance of the meta-bean. */ static final Meta INSTANCE = new Meta(); /** * The meta-property for the {@code startDate} property. */ private final MetaProperty<LocalDate> startDate = DirectMetaProperty.ofImmutable( this, "startDate", PeriodicSchedule.class, LocalDate.class); /** * The meta-property for the {@code endDate} property. */ private final MetaProperty<LocalDate> endDate = DirectMetaProperty.ofImmutable( this, "endDate", PeriodicSchedule.class, LocalDate.class); /** * The meta-property for the {@code frequency} property. */ private final MetaProperty<Frequency> frequency = DirectMetaProperty.ofImmutable( this, "frequency", PeriodicSchedule.class, Frequency.class); /** * The meta-property for the {@code businessDayAdjustment} property. */ private final MetaProperty<BusinessDayAdjustment> businessDayAdjustment = DirectMetaProperty.ofImmutable( this, "businessDayAdjustment", PeriodicSchedule.class, BusinessDayAdjustment.class); /** * The meta-property for the {@code startDateBusinessDayAdjustment} property. */ private final MetaProperty<BusinessDayAdjustment> startDateBusinessDayAdjustment = DirectMetaProperty.ofImmutable( this, "startDateBusinessDayAdjustment", PeriodicSchedule.class, BusinessDayAdjustment.class); /** * The meta-property for the {@code endDateBusinessDayAdjustment} property. */ private final MetaProperty<BusinessDayAdjustment> endDateBusinessDayAdjustment = DirectMetaProperty.ofImmutable( this, "endDateBusinessDayAdjustment", PeriodicSchedule.class, BusinessDayAdjustment.class); /** * The meta-property for the {@code stubConvention} property. */ private final MetaProperty<StubConvention> stubConvention = DirectMetaProperty.ofImmutable( this, "stubConvention", PeriodicSchedule.class, StubConvention.class); /** * The meta-property for the {@code rollConvention} property. */ private final MetaProperty<RollConvention> rollConvention = DirectMetaProperty.ofImmutable( this, "rollConvention", PeriodicSchedule.class, RollConvention.class); /** * The meta-property for the {@code firstRegularStartDate} property. */ private final MetaProperty<LocalDate> firstRegularStartDate = DirectMetaProperty.ofImmutable( this, "firstRegularStartDate", PeriodicSchedule.class, LocalDate.class); /** * The meta-property for the {@code lastRegularEndDate} property. */ private final MetaProperty<LocalDate> lastRegularEndDate = DirectMetaProperty.ofImmutable( this, "lastRegularEndDate", PeriodicSchedule.class, LocalDate.class); /** * The meta-property for the {@code overrideStartDate} property. */ private final MetaProperty<AdjustableDate> overrideStartDate = DirectMetaProperty.ofImmutable( this, "overrideStartDate", PeriodicSchedule.class, AdjustableDate.class); /** * The meta-properties. */ private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap( this, null, "startDate", "endDate", "frequency", "businessDayAdjustment", "startDateBusinessDayAdjustment", "endDateBusinessDayAdjustment", "stubConvention", "rollConvention", "firstRegularStartDate", "lastRegularEndDate", "overrideStartDate"); /** * Restricted constructor. */ private Meta() { } @Override protected MetaProperty<?> metaPropertyGet(String propertyName) { switch (propertyName.hashCode()) { case -2129778896: // startDate return startDate; case -1607727319: // endDate return endDate; case -70023844: // frequency return frequency; case -1065319863: // businessDayAdjustment return businessDayAdjustment; case 429197561: // startDateBusinessDayAdjustment return startDateBusinessDayAdjustment; case -734327136: // endDateBusinessDayAdjustment return endDateBusinessDayAdjustment; case -31408449: // stubConvention return stubConvention; case -10223666: // rollConvention return rollConvention; case 2011803076: // firstRegularStartDate return firstRegularStartDate; case -1540679645: // lastRegularEndDate return lastRegularEndDate; case -599936828: // overrideStartDate return overrideStartDate; } return super.metaPropertyGet(propertyName); } @Override public PeriodicSchedule.Builder builder() { return new PeriodicSchedule.Builder(); } @Override public Class<? extends PeriodicSchedule> beanType() { return PeriodicSchedule.class; } @Override public Map<String, MetaProperty<?>> metaPropertyMap() { return metaPropertyMap$; } //----------------------------------------------------------------------- /** * The meta-property for the {@code startDate} property. * @return the meta-property, not null */ public MetaProperty<LocalDate> startDate() { return startDate; } /** * The meta-property for the {@code endDate} property. * @return the meta-property, not null */ public MetaProperty<LocalDate> endDate() { return endDate; } /** * The meta-property for the {@code frequency} property. * @return the meta-property, not null */ public MetaProperty<Frequency> frequency() { return frequency; } /** * The meta-property for the {@code businessDayAdjustment} property. * @return the meta-property, not null */ public MetaProperty<BusinessDayAdjustment> businessDayAdjustment() { return businessDayAdjustment; } /** * The meta-property for the {@code startDateBusinessDayAdjustment} property. * @return the meta-property, not null */ public MetaProperty<BusinessDayAdjustment> startDateBusinessDayAdjustment() { return startDateBusinessDayAdjustment; } /** * The meta-property for the {@code endDateBusinessDayAdjustment} property. * @return the meta-property, not null */ public MetaProperty<BusinessDayAdjustment> endDateBusinessDayAdjustment() { return endDateBusinessDayAdjustment; } /** * The meta-property for the {@code stubConvention} property. * @return the meta-property, not null */ public MetaProperty<StubConvention> stubConvention() { return stubConvention; } /** * The meta-property for the {@code rollConvention} property. * @return the meta-property, not null */ public MetaProperty<RollConvention> rollConvention() { return rollConvention; } /** * The meta-property for the {@code firstRegularStartDate} property. * @return the meta-property, not null */ public MetaProperty<LocalDate> firstRegularStartDate() { return firstRegularStartDate; } /** * The meta-property for the {@code lastRegularEndDate} property. * @return the meta-property, not null */ public MetaProperty<LocalDate> lastRegularEndDate() { return lastRegularEndDate; } /** * The meta-property for the {@code overrideStartDate} property. * @return the meta-property, not null */ public MetaProperty<AdjustableDate> overrideStartDate() { return overrideStartDate; } //----------------------------------------------------------------------- @Override protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { switch (propertyName.hashCode()) { case -2129778896: // startDate return ((PeriodicSchedule) bean).getStartDate(); case -1607727319: // endDate return ((PeriodicSchedule) bean).getEndDate(); case -70023844: // frequency return ((PeriodicSchedule) bean).getFrequency(); case -1065319863: // businessDayAdjustment return ((PeriodicSchedule) bean).getBusinessDayAdjustment(); case 429197561: // startDateBusinessDayAdjustment return ((PeriodicSchedule) bean).startDateBusinessDayAdjustment; case -734327136: // endDateBusinessDayAdjustment return ((PeriodicSchedule) bean).endDateBusinessDayAdjustment; case -31408449: // stubConvention return ((PeriodicSchedule) bean).stubConvention; case -10223666: // rollConvention return ((PeriodicSchedule) bean).rollConvention; case 2011803076: // firstRegularStartDate return ((PeriodicSchedule) bean).firstRegularStartDate; case -1540679645: // lastRegularEndDate return ((PeriodicSchedule) bean).lastRegularEndDate; case -599936828: // overrideStartDate return ((PeriodicSchedule) bean).overrideStartDate; } return super.propertyGet(bean, propertyName, quiet); } @Override protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { metaProperty(propertyName); if (quiet) { return; } throw new UnsupportedOperationException("Property cannot be written: " + propertyName); } } //----------------------------------------------------------------------- /** * The bean-builder for {@code PeriodicSchedule}. */ public static final class Builder extends DirectFieldsBeanBuilder<PeriodicSchedule> { private LocalDate startDate; private LocalDate endDate; private Frequency frequency; private BusinessDayAdjustment businessDayAdjustment; private BusinessDayAdjustment startDateBusinessDayAdjustment; private BusinessDayAdjustment endDateBusinessDayAdjustment; private StubConvention stubConvention; private RollConvention rollConvention; private LocalDate firstRegularStartDate; private LocalDate lastRegularEndDate; private AdjustableDate overrideStartDate; /** * Restricted constructor. */ private Builder() { } /** * Restricted copy constructor. * @param beanToCopy the bean to copy from, not null */ private Builder(PeriodicSchedule beanToCopy) { this.startDate = beanToCopy.getStartDate(); this.endDate = beanToCopy.getEndDate(); this.frequency = beanToCopy.getFrequency(); this.businessDayAdjustment = beanToCopy.getBusinessDayAdjustment(); this.startDateBusinessDayAdjustment = beanToCopy.startDateBusinessDayAdjustment; this.endDateBusinessDayAdjustment = beanToCopy.endDateBusinessDayAdjustment; this.stubConvention = beanToCopy.stubConvention; this.rollConvention = beanToCopy.rollConvention; this.firstRegularStartDate = beanToCopy.firstRegularStartDate; this.lastRegularEndDate = beanToCopy.lastRegularEndDate; this.overrideStartDate = beanToCopy.overrideStartDate; } //----------------------------------------------------------------------- @Override public Object get(String propertyName) { switch (propertyName.hashCode()) { case -2129778896: // startDate return startDate; case -1607727319: // endDate return endDate; case -70023844: // frequency return frequency; case -1065319863: // businessDayAdjustment return businessDayAdjustment; case 429197561: // startDateBusinessDayAdjustment return startDateBusinessDayAdjustment; case -734327136: // endDateBusinessDayAdjustment return endDateBusinessDayAdjustment; case -31408449: // stubConvention return stubConvention; case -10223666: // rollConvention return rollConvention; case 2011803076: // firstRegularStartDate return firstRegularStartDate; case -1540679645: // lastRegularEndDate return lastRegularEndDate; case -599936828: // overrideStartDate return overrideStartDate; default: throw new NoSuchElementException("Unknown property: " + propertyName); } } @Override public Builder set(String propertyName, Object newValue) { switch (propertyName.hashCode()) { case -2129778896: // startDate this.startDate = (LocalDate) newValue; break; case -1607727319: // endDate this.endDate = (LocalDate) newValue; break; case -70023844: // frequency this.frequency = (Frequency) newValue; break; case -1065319863: // businessDayAdjustment this.businessDayAdjustment = (BusinessDayAdjustment) newValue; break; case 429197561: // startDateBusinessDayAdjustment this.startDateBusinessDayAdjustment = (BusinessDayAdjustment) newValue; break; case -734327136: // endDateBusinessDayAdjustment this.endDateBusinessDayAdjustment = (BusinessDayAdjustment) newValue; break; case -31408449: // stubConvention this.stubConvention = (StubConvention) newValue; break; case -10223666: // rollConvention this.rollConvention = (RollConvention) newValue; break; case 2011803076: // firstRegularStartDate this.firstRegularStartDate = (LocalDate) newValue; break; case -1540679645: // lastRegularEndDate this.lastRegularEndDate = (LocalDate) newValue; break; case -599936828: // overrideStartDate this.overrideStartDate = (AdjustableDate) newValue; break; default: throw new NoSuchElementException("Unknown property: " + propertyName); } return this; } @Override public Builder set(MetaProperty<?> property, Object value) { super.set(property, value); return this; } @Override public Builder setString(String propertyName, String value) { setString(meta().metaProperty(propertyName), value); return this; } @Override public Builder setString(MetaProperty<?> property, String value) { super.setString(property, value); return this; } @Override public Builder setAll(Map<String, ? extends Object> propertyValueMap) { super.setAll(propertyValueMap); return this; } @Override public PeriodicSchedule build() { return new PeriodicSchedule( startDate, endDate, frequency, businessDayAdjustment, startDateBusinessDayAdjustment, endDateBusinessDayAdjustment, stubConvention, rollConvention, firstRegularStartDate, lastRegularEndDate, overrideStartDate); } //----------------------------------------------------------------------- /** * Sets the start date, which is the start of the first schedule period. * <p> * This is the start date of the schedule. * It is is unadjusted and as such might be a weekend or holiday. * Any applicable business day adjustment will be applied when creating the schedule. * This is also known as the unadjusted effective date. * <p> * In most cases, the start date of a financial instrument is just after the trade date, * such as two business days later. However, the start date of a schedule is permitted * to be any date, which includes dates before or after the trade date. * @param startDate the new value, not null * @return this, for chaining, not null */ public Builder startDate(LocalDate startDate) { JodaBeanUtils.notNull(startDate, "startDate"); this.startDate = startDate; return this; } /** * Sets the end date, which is the end of the last schedule period. * <p> * This is the end date of the schedule. * It is is unadjusted and as such might be a weekend or holiday. * Any applicable business day adjustment will be applied when creating the schedule. * This is also known as the unadjusted maturity date or unadjusted termination date. * This date must be after the start date. * @param endDate the new value, not null * @return this, for chaining, not null */ public Builder endDate(LocalDate endDate) { JodaBeanUtils.notNull(endDate, "endDate"); this.endDate = endDate; return this; } /** * Sets the regular periodic frequency to use. * <p> * Most dates are calculated using a regular periodic frequency, such as every 3 months. * The actual day-of-month or day-of-week is selected using the roll and stub conventions. * @param frequency the new value, not null * @return this, for chaining, not null */ public Builder frequency(Frequency frequency) { JodaBeanUtils.notNull(frequency, "frequency"); this.frequency = frequency; return this; } /** * Sets the business day adjustment to apply. * <p> * Each date in the calculated schedule is determined without taking into account weekends and holidays. * The adjustment specified here is used to convert those dates to valid business days. * <p> * The start date and end date may have their own business day adjustment rules. * If those are not present, then this adjustment is used instead. * @param businessDayAdjustment the new value, not null * @return this, for chaining, not null */ public Builder businessDayAdjustment(BusinessDayAdjustment businessDayAdjustment) { JodaBeanUtils.notNull(businessDayAdjustment, "businessDayAdjustment"); this.businessDayAdjustment = businessDayAdjustment; return this; } /** * Sets the optional business day adjustment to apply to the start date. * <p> * The start date property is an unadjusted date and as such might be a weekend or holiday. * The adjustment specified here is used to convert the start date to a valid business day. * <p> * If this property is not present, the standard {@code businessDayAdjustment} property is used instead. * @param startDateBusinessDayAdjustment the new value * @return this, for chaining, not null */ public Builder startDateBusinessDayAdjustment(BusinessDayAdjustment startDateBusinessDayAdjustment) { this.startDateBusinessDayAdjustment = startDateBusinessDayAdjustment; return this; } /** * Sets the optional business day adjustment to apply to the end date. * <p> * The end date property is an unadjusted date and as such might be a weekend or holiday. * The adjustment specified here is used to convert the end date to a valid business day. * <p> * If this property is not present, the standard {@code businessDayAdjustment} property is used instead. * @param endDateBusinessDayAdjustment the new value * @return this, for chaining, not null */ public Builder endDateBusinessDayAdjustment(BusinessDayAdjustment endDateBusinessDayAdjustment) { this.endDateBusinessDayAdjustment = endDateBusinessDayAdjustment; return this; } /** * Sets the optional convention defining how to handle stubs. * <p> * The stub convention is used during schedule construction to determine whether the irregular * remaining period occurs at the start or end of the schedule. * It also determines whether the irregular period is shorter or longer than the regular period. * This property interacts with the "explicit dates" of {@link PeriodicSchedule#getFirstRegularStartDate()} * and {@link PeriodicSchedule#getLastRegularEndDate()}. * <p> * The convention 'None' may be used to explicitly indicate there are no stubs. * There must be no explicit dates. * This will be validated during schedule construction. * <p> * The convention 'Both' may be used to explicitly indicate there is both an initial and final stub. * The stubs themselves must be specified using explicit dates. * This will be validated during schedule construction. * <p> * The conventions 'ShortInitial', 'LongInitial', 'ShortFinal' and 'LongFinal' are used to * indicate the type of stub to be generated. The exact behavior varies depending on whether * there are explicit dates or not: * <p> * If explicit dates are specified, then the combination of stub convention an explicit date * will be validated during schedule construction. For example, the combination of an explicit dated * initial stub and a stub convention of 'ShortInitial' or 'LongInitial' is valid, but other * stub conventions, such as 'ShortFinal' or 'None' would be invalid. * <p> * If explicit dates are not specified, then it is not required that a stub is generated. * The convention determines whether to generate dates from the start date forward, or the * end date backwards. Date generation may or may not result in a stub, but if it does then * the stub will be of the correct type. * <p> * When the stub convention is not present, the generation of stubs is based entirely on * the presence or absence of the explicit dates. * @param stubConvention the new value * @return this, for chaining, not null */ public Builder stubConvention(StubConvention stubConvention) { this.stubConvention = stubConvention; return this; } /** * Sets the optional convention defining how to roll dates. * <p> * The schedule periods are determined at the high level by repeatedly adding * the frequency to the start date, or subtracting it from the end date. * The roll convention provides the detailed rule to adjust the day-of-month or day-of-week. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the roll convention will be implied. * @param rollConvention the new value * @return this, for chaining, not null */ public Builder rollConvention(RollConvention rollConvention) { this.rollConvention = rollConvention; return this; } /** * Sets the optional start date of the first regular schedule period, which is the end date of the initial stub. * <p> * This is used to identify the boundary date between the initial stub and the first regular schedule period. * <p> * This is an unadjusted date, and as such it might not be a valid business day. * This date must be on or after 'startDate'. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the overall schedule start date will be used instead, resulting in no initial stub. * @param firstRegularStartDate the new value * @return this, for chaining, not null */ public Builder firstRegularStartDate(LocalDate firstRegularStartDate) { this.firstRegularStartDate = firstRegularStartDate; return this; } /** * Sets the optional end date of the last regular schedule period, which is the start date of the final stub. * <p> * This is used to identify the boundary date between the last regular schedule period and the final stub. * <p> * This is an unadjusted date, and as such it might not be a valid business day. * This date must be after 'startDate' and after 'firstRegularStartDate'. * This date must be on or before 'endDate'. * <p> * During schedule generation, if this is present it will be used to determine the schedule. * If not present, then the overall schedule end date will be used instead, resulting in no final stub. * @param lastRegularEndDate the new value * @return this, for chaining, not null */ public Builder lastRegularEndDate(LocalDate lastRegularEndDate) { this.lastRegularEndDate = lastRegularEndDate; return this; } /** * Sets the optional start date of the first schedule period, overriding normal schedule generation. * <p> * This property is rarely used, and is generally needed when accrual starts before the effective date. * If specified, it overrides the start date of the first period once schedule generation has been completed. * Note that all schedule generation rules apply to 'startDate', with this applied as a final step. * This field primarily exists to support the FpML 'firstPeriodStartDate' concept. * <p> * If set, it should be different to the start date, although this is not validated. * Validation does check that it is before the 'firstRegularStartDate'. * <p> * During schedule generation, if this is present it will be used to override the start date * of the first generated schedule period. * If not present, then the start of the first period will be the normal start date. * @param overrideStartDate the new value * @return this, for chaining, not null */ public Builder overrideStartDate(AdjustableDate overrideStartDate) { this.overrideStartDate = overrideStartDate; return this; } //----------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(384); buf.append("PeriodicSchedule.Builder{"); buf.append("startDate").append('=').append(JodaBeanUtils.toString(startDate)).append(',').append(' '); buf.append("endDate").append('=').append(JodaBeanUtils.toString(endDate)).append(',').append(' '); buf.append("frequency").append('=').append(JodaBeanUtils.toString(frequency)).append(',').append(' '); buf.append("businessDayAdjustment").append('=').append(JodaBeanUtils.toString(businessDayAdjustment)).append(',').append(' '); buf.append("startDateBusinessDayAdjustment").append('=').append(JodaBeanUtils.toString(startDateBusinessDayAdjustment)).append(',').append(' '); buf.append("endDateBusinessDayAdjustment").append('=').append(JodaBeanUtils.toString(endDateBusinessDayAdjustment)).append(',').append(' '); buf.append("stubConvention").append('=').append(JodaBeanUtils.toString(stubConvention)).append(',').append(' '); buf.append("rollConvention").append('=').append(JodaBeanUtils.toString(rollConvention)).append(',').append(' '); buf.append("firstRegularStartDate").append('=').append(JodaBeanUtils.toString(firstRegularStartDate)).append(',').append(' '); buf.append("lastRegularEndDate").append('=').append(JodaBeanUtils.toString(lastRegularEndDate)).append(',').append(' '); buf.append("overrideStartDate").append('=').append(JodaBeanUtils.toString(overrideStartDate)); buf.append('}'); return buf.toString(); } } ///CLOVER:ON //-------------------------- AUTOGENERATED END -------------------------- }