/**
* 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.time.LocalDate;
import org.joda.convert.FromString;
import org.joda.convert.ToString;
import com.google.common.base.CaseFormat;
import com.opengamma.strata.collect.ArgChecker;
/**
* A convention defining how to calculate stub periods.
* <p>
* A {@linkplain PeriodicSchedule 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>
* The stub convention is provided as a simple declarative mechanism to define stubs.
* The convention handles the case of no stubs, or a single stub at the start or end.
* If there is a stub at both the start and end, then explicit stub dates must be used.
* <p>
* For example, dividing a 24 month (2 year) swap into 3 month periods is easy as it splits exactly.
* However, 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.
* <p>
* The 'ShortInitial' or 'LongInitial' convention causes the regular periods to be determined
* <i>backwards</i> from the end date of the schedule, with remaining days allocated to the stub.
* <p>
* The 'ShortFinal' or 'LongFinal' convention causes the regular periods to be determined
* <i>forwards</i> from the start date of the schedule, with remaining days allocated to the stub.
* <p>
* The 'None' convention may be used to explicitly indicate there are no stubs.
* <p>
* The 'Both' convention may be used to explicitly indicate there is both an initial and final stub.
* In this case, dates must be used to identify the stubs.
*/
public enum StubConvention {
/**
* Explicitly states that there are no stubs.
* <p>
* This is used to indicate that the term of the schedule evenly divides by the
* periodic frequency leaving no stubs.
* For example, a 6 month trade can be exactly divided by a 3 month frequency.
* <p>
* If the term of the schedule is less than the frequency, then only one period exists.
* In this case, the period is not treated as a stub.
* <p>
* When creating a schedule, there must be no explicit stubs.
*/
NONE {
@Override
StubConvention toImplicit(PeriodicSchedule definition, boolean explicitInitialStub, boolean explicitFinalStub) {
if (explicitInitialStub || explicitFinalStub) {
throw new ScheduleException(
definition, "Dates specify an explicit stub, but stub convention is 'None'");
}
return NONE;
}
},
/**
* A short initial stub.
* <p>
* The schedule periods will be determined backwards from the end date.
* Any remaining period, shorter than the standard frequency, will be allocated at the start.
* <p>
* For example, an 8 month trade with a 3 month periodic frequency would result in
* a 2 month initial short stub followed by two periods of 3 months.
* <p>
* If there is no remaining period when calculating, then there is no stub.
* For example, a 6 month trade can be exactly divided by a 3 month frequency.
* <p>
* When creating a schedule, there must be no explicit final stub.
* If there is an explicit initial stub, then this convention is considered to be matched
* and the remaining period is calculated using the stub convention 'None'.
*/
SHORT_INITIAL {
@Override
StubConvention toImplicit(PeriodicSchedule definition, boolean explicitInitialStub, boolean explicitFinalStub) {
if (explicitFinalStub) {
throw new ScheduleException(
definition, "Dates specify an explicit final stub, but stub convention is 'ShortInitial'");
}
return (explicitInitialStub ? NONE : SHORT_INITIAL);
}
},
/**
* A long initial stub.
* <p>
* The schedule periods will be determined backwards from the end date.
* Any remaining period, shorter than the standard frequency, will be allocated at the start
* and combined with the next period, making a total period longer than the standard frequency.
* <p>
* For example, an 8 month trade with a 3 month periodic frequency would result in
* a 5 month initial long stub followed by one period of 3 months.
* <p>
* If there is no remaining period when calculating, then there is no stub.
* For example, a 6 month trade can be exactly divided by a 3 month frequency.
* <p>
* When creating a schedule, there must be no explicit final stub.
* If there is an explicit initial stub, then this convention is considered to be matched
* and the remaining period is calculated using the stub convention 'None'.
*/
LONG_INITIAL {
@Override
StubConvention toImplicit(PeriodicSchedule definition, boolean explicitInitialStub, boolean explicitFinalStub) {
if (explicitFinalStub) {
throw new ScheduleException(
definition, "Dates specify an explicit final stub, but stub convention is 'LongInitial'");
}
return (explicitInitialStub ? NONE : LONG_INITIAL);
}
},
/**
* A short final stub.
* <p>
* The schedule periods will be determined forwards from the regular period start date.
* Any remaining period, shorter than the standard frequency, will be allocated at the end.
* <p>
* For example, an 8 month trade with a 3 month periodic frequency would result in
* two periods of 3 months followed by a 2 month final short stub.
* <p>
* If there is no remaining period when calculating, then there is no stub.
* For example, a 6 month trade can be exactly divided by a 3 month frequency.
* <p>
* When creating a schedule, there must be no explicit initial stub.
* If there is an explicit final stub, then this convention is considered to be matched
* and the remaining period is calculated using the stub convention 'None'.
*/
SHORT_FINAL {
@Override
StubConvention toImplicit(PeriodicSchedule definition, boolean explicitInitialStub, boolean explicitFinalStub) {
if (explicitInitialStub) {
throw new ScheduleException(
definition, "Dates specify an explicit initial stub, but stub convention is 'ShortFinal'");
}
return (explicitFinalStub ? NONE : SHORT_FINAL);
}
},
/**
* A long final stub.
* <p>
* The schedule periods will be determined forwards from the regular period start date.
* Any remaining period, shorter than the standard frequency, will be allocated at the end
* and combined with the previous period, making a total period longer than the standard frequency.
* <p>
* For example, an 8 month trade with a 3 month periodic frequency would result in
* one period of 3 months followed by a 5 month final long stub.
* <p>
* If there is no remaining period when calculating, then there is no stub.
* For example, a 6 month trade can be exactly divided by a 3 month frequency.
* <p>
* When creating a schedule, there must be no explicit initial stub.
* If there is an explicit final stub, then this convention is considered to be matched
* and the remaining period is calculated using the stub convention 'None'.
*/
LONG_FINAL {
@Override
StubConvention toImplicit(PeriodicSchedule definition, boolean explicitInitialStub, boolean explicitFinalStub) {
if (explicitInitialStub) {
throw new ScheduleException(
definition, "Dates specify an explicit initial stub, but stub convention is 'LongFinal'");
}
return (explicitFinalStub ? NONE : LONG_FINAL);
}
},
/**
* Both ends of the schedule have a stub.
* <p>
* The schedule periods will be determined from two dates - the regular period start date
* and the regular period end date.
* Days before the first regular period start date form the initial stub.
* Days after the last regular period end date form the final stub.
* <p>
* When creating a schedule, there must be both an explicit initial and final stub.
*/
BOTH {
@Override
StubConvention toImplicit(PeriodicSchedule definition, boolean explicitInitialStub, boolean explicitFinalStub) {
if ((explicitInitialStub && explicitFinalStub) == false) {
throw new ScheduleException(
definition, "Stub convention is 'Both' but explicit dates not specified");
}
return NONE;
}
};
//-------------------------------------------------------------------------
/**
* Obtains an instance from the specified unique name.
*
* @param uniqueName the unique name
* @return the type
* @throws IllegalArgumentException if the name is not known
*/
@FromString
public static StubConvention of(String uniqueName) {
ArgChecker.notNull(uniqueName, "uniqueName");
return valueOf(CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, uniqueName));
}
//-------------------------------------------------------------------------
/**
* Converts this stub convention to the appropriate roll convention.
* <p>
* This converts a stub convention to a {@link RollConvention} based on the
* start date, end date, frequency and preference for end-of-month.
* The net result is to imply the roll convention from the schedule data.
* <p>
* The rules are as follows:
* <p>
* If the input frequency is month-based, then the implied convention is based on
* the day-of-month of the initial date, where the initial date is the start date
* if rolling forwards or the end date otherwise.
* If that date is on the 31st day, or if the 'preferEndOfMonth' flag is true and
* the relevant date is at the end of the month, then the implied convention is 'EOM'.
* For example, if the initial date of the sequence is 2014-06-20 and the periodic
* frequency is 'P3M' (month-based), then the implied convention is 'Day20'.
* <p>
* If the input frequency is week-based, then the implied convention is based on
* the day-of-week of the initial date, where the initial date is the start date
* if rolling forwards or the end date otherwise.
* For example, if the initial date of the sequence is 2014-06-20 and the periodic
* frequency is 'P2W' (week-based), then the implied convention is 'DayFri',
* because 2014-06-20 is a Friday.
* <p>
* In all other cases, the implied convention is 'None'.
*
* @param start the start date of the schedule
* @param end the end date of the schedule
* @param frequency the periodic frequency of the schedule
* @param preferEndOfMonth whether to prefer the end-of-month when rolling
* @return the derived roll convention
*/
public final RollConvention toRollConvention(
LocalDate start, LocalDate end, Frequency frequency, boolean preferEndOfMonth) {
ArgChecker.notNull(start, "start");
ArgChecker.notNull(end, "end");
ArgChecker.notNull(frequency, "frequency");
if (isCalculateBackwards()) {
return toRollConvention(end, frequency, preferEndOfMonth);
} else {
return toRollConvention(start, frequency, preferEndOfMonth);
}
}
// helper for converting to roll convention
private static RollConvention toRollConvention(LocalDate date, Frequency frequency, boolean preferEndOfMonth) {
if (frequency.isMonthBased()) {
if (preferEndOfMonth && date.getDayOfMonth() == date.lengthOfMonth()) {
return RollConventions.EOM;
}
return RollConvention.ofDayOfMonth(date.getDayOfMonth());
} else if (frequency.isWeekBased()) {
return RollConvention.ofDayOfWeek(date.getDayOfWeek());
} else {
// neither monthly nor weekly means no known roll convention
return RollConventions.NONE;
}
}
/**
* Converts this stub convention to one that creates implicit stubs, validating that
* any explicit stubs are correct.
* <p>
* Stubs can be specified in two ways, using dates or using this convention.
* This method is passed flags indicating whether explicit stubs have been specified using dates.
* It validated that such stubs are compatible, and returns a convention suitable for
* creating stubs implicitly during rolling.
* <p>
* For example, an invalid stub convention would be to specify two stubs using explicit dates but
* declaring the convention as 'ShortFinal'.
* <p>
* The result is the implicit stub convention to apply between the two calculation dates.
* For example, if an initial stub is defined by dates then it cannot also be created automatically,
* thus the implicit stub convention is 'None'.
*
* @param definition the schedule definition, for error messages
* @param explicitInitialStub an initial stub has been explicitly defined by dates
* @param explicitFinalStub a final stub has been explicitly defined by dates
* @return the effective stub convention
* @throws ScheduleException if the input data is invalid
*/
abstract StubConvention toImplicit(
PeriodicSchedule definition, boolean explicitInitialStub, boolean explicitFinalStub);
//-------------------------------------------------------------------------
/**
* Checks if the schedule is calculated forwards from the start date to the end date.
* <p>
* If true, then there will typically be a stub at the end of the schedule.
* <p>
* The 'None', 'ShortFinal' and 'LongFinal' conventions return true.
* Other conventions return false.
*
* @return true if calculation occurs forwards from the start date to the end date
*/
public boolean isCalculateForwards() {
return this == SHORT_FINAL || this == LONG_FINAL || this == NONE;
}
/**
* Checks if the schedule is calculated backwards from the end date to the start date.
* <p>
* If true, then there will typically be a stub at the start of the schedule.
* <p>
* The 'ShortInitial' and 'LongInitial' conventions return true.
* Other conventions return false.
*
* @return true if calculation occurs backwards from the end date to the start date
*/
public boolean isCalculateBackwards() {
return this == SHORT_INITIAL || this == LONG_INITIAL;
}
/**
* Checks if this convention may result in a long stub.
* <p>
* The 'LongInitial' and 'LongFinal' conventions return true.
* Other conventions return false.
*
* @return true if there may be a long stub
*/
public boolean isLong() {
return this == LONG_INITIAL || this == LONG_FINAL;
}
/**
* Checks if this convention may result in a short stub.
* <p>
* The 'ShortInitial' and 'ShortFinal' conventions return true.
* Other conventions return false.
*
* @return true if there may be a short stub
*/
public boolean isShort() {
return this == SHORT_INITIAL || this == SHORT_FINAL;
}
//-------------------------------------------------------------------------
/**
* Returns the formatted unique name of the type.
*
* @return the formatted string representing the type
*/
@ToString
@Override
public String toString() {
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
}
}