Struct Schedule
pub struct Schedule {
pub dates: Vec<NaiveDate>,
}Expand description
An ordered set of adjusted calculation-period boundary dates.
Fields§
§dates: Vec<NaiveDate>Boundary dates, ascending. n boundaries delimit n - 1 periods.
Implementations§
§impl Schedule
The schedule’s payment-date projection.
impl Schedule
The schedule’s payment-date projection.
Schedule is a statics-layer type and this is an inherent method on it, but
the impl block lives here rather than beside Schedule::generate:
LagAdjustment belongs to this layer, and writing the method in
statics::schedules would make the reference-data layer import the cashflow
layer above it. Split this way, callers still write schedule.paying(&lag)
and the dependency edge points down, which is the direction the layering
allows.
pub fn paying(&self, lag: &LagAdjustment) -> Vec<NaiveDate>
pub fn paying(&self, lag: &LagAdjustment) -> Vec<NaiveDate>
The payment date of every period, under a lag supplied at the call.
One date per period, so this is one shorter than Schedule::dates.
The schedule carries no payment lag of its own and stores nothing here. When a period pays is the coupon’s business: two legs sharing one schedule may pay under different lags against different calendars, which a stored lag could not express. The lag is therefore handed in, and this is a projection over it rather than a property of the schedule.
It routes through LagAdjustment::apply — the same operation a
coupon’s payment date is computed by — rather than a second walk, so a
lag cannot mean one thing in a schedule and another in a leg. That is the
discipline RollConvention::apply already holds between the schedule
generator and a caller.
A zero lag is the identity on a date that is already a good business day,
and LagAdjustment::from_payment_lag returns None for one; a caller
with no lag reads the accrual ends off Schedule::periods, which is what
“no lag” means.
§impl Schedule
impl Schedule
pub fn generate(
effective: NaiveDate,
termination: NaiveDate,
frequency: Period,
calendar: &dyn Calendar,
convention: BusinessDayConvention,
roll: RollConvention,
stub: StubPeriodType,
end_of_month: bool,
terminal_convention: Option<BusinessDayConvention>,
date_generation: Option<DateGenerationRule>,
) -> Result<Self, ScheduleError>
pub fn generate( effective: NaiveDate, termination: NaiveDate, frequency: Period, calendar: &dyn Calendar, convention: BusinessDayConvention, roll: RollConvention, stub: StubPeriodType, end_of_month: bool, terminal_convention: Option<BusinessDayConvention>, date_generation: Option<DateGenerationRule>, ) -> Result<Self, ScheduleError>
Generates a schedule between effective and termination.
Initial stubs generate backward from termination; final stubs generate
forward from effective. Interior boundaries are snapped by roll, then
every boundary is adjusted by convention against calendar.
§Both dates are unadjusted anchors
effective and termination are unadjusted generation anchors, not
the adjusted boundaries this returns. One of the two is the seed regular
boundaries are measured from; the other is only a boundary:
| stub | seed | boundary only |
|---|---|---|
ShortFinal / LongFinal | effective | termination |
ShortInitial / LongInitial | termination | effective |
Passing an already-adjusted date in place of the seed moves every
interior boundary, because they are measured as seed + k·frequency.
Passing one in place of the other date is harmless — it is adjusted
again, which is idempotent. So the same substitution is corrupting in one
direction and invisible in the other, and neither raises.
A caller holding only adjusted dates — read off a confirm, say —
recovers the unadjusted schedule by supplying the roll that generated
them. roll snaps interior boundaries after
generation, so it supersedes the seed’s day of month: an adjusted
effective of 2000-01-03 under RollConvention::DayOfMonth(1) produces
the same dates as the unadjusted 2000-01-01 under
RollConvention::None, date for date. Only
RollConvention::None carries no snap and so cannot recover anything;
see an_adjusted_seed_under_its_roll_is_the_unadjusted_schedule.
To state boundary dates outright rather than generate them, use
Schedule::from_dates, which applies no calendar, roll or adjustment
at all.
Every regular boundary is a whole multiple of frequency measured from a
single fixed anchor — effective when generating forward, termination
when generating backward — never from the previously generated boundary.
Month arithmetic clamps into short months (31 Jan + 1M = 28 Feb), and
anchoring keeps each clamp local to the boundary that incurs it instead of
ratcheting the day-of-month down for every later one — seed + n·tenor
rather than a cumulative step; see scale.
A frequency that does not carry the cursor toward the far anchor — a
zero multiplier, a Term period, or a negative multiplier — yields the
lone [effective, termination] period rather than looping (see
advances).
When end_of_month is set and the seed anchors a month end (it is on or
after its month’s last business day), every interior boundary is rolled
to the calendar last day of its month — the end-of-month roll (FpML
EOM, RollConvention::Eom) — and convention then adjusts it like
any other boundary. So Following on a Saturday 31 January gives Monday
2 February, ModifiedFollowing gives Friday 30 January, and None keeps
the Saturday. roll is not applied in that mode (the flag is the
roll). The endpoints are never end-of-month rolled, but they are still
adjusted to business days: the effective date by convention, the
termination by terminal_convention.
Whichever roll applies, it is applied to each candidate boundary before the stop test against the far anchor, so a boundary the raw arithmetic placed inside the schedule cannot be rolled onto or past its end: the result is strictly ascending, and a long stub merges the two rolled periods it names.
This is a different rule from the money-market end/end rule that
Calendar::advance applies under the same flag, which always lands on
the last business day: a deposit or index window from a month end
matures on the last business day whatever the convention, while a
schedule boundary is the calendar month end, adjusted. The two differ
under Following and None on a weekend month end.
terminal_convention adjusts the final boundary (the termination date)
independently of convention; None adjusts it by convention (the
historical behaviour), while Some(BusinessDayConvention::None) keeps the
termination date exactly as passed — the separate end-date business-day
adjustment that market conventions specify independently of the interior.
When date_generation is Some(DateGenerationRule::Cds), the schedule
anchors its start at the prior CDS-IMM date of effective, steps forward
quarterly snapping interior boundaries to the 20th (the ImmTwentieth
roll, superseding roll and end_of_month), and—paired with a
terminal_convention of None business-day adjustment—reproduces the
standard CDS2015 boundary list. None and the non-CDS rules generate as
before (direction inferred from stub).
§Errors
ScheduleError::RepeatedDate when termination equals effective,
and ScheduleError::OutOfOrder when it precedes it: a term that does
not run forwards delimits no period, and silently accepting one once
produced a two-date schedule with a single negative-length period.
pub fn generate_with_stubs(
effective: NaiveDate,
first_regular_start: Option<NaiveDate>,
last_regular_end: Option<NaiveDate>,
termination: NaiveDate,
frequency: Period,
calendar: &dyn Calendar,
convention: BusinessDayConvention,
roll: RollConvention,
stub: StubPeriodType,
end_of_month: bool,
terminal_convention: Option<BusinessDayConvention>,
) -> Result<Self, ScheduleError>
pub fn generate_with_stubs( effective: NaiveDate, first_regular_start: Option<NaiveDate>, last_regular_end: Option<NaiveDate>, termination: NaiveDate, frequency: Period, calendar: &dyn Calendar, convention: BusinessDayConvention, roll: RollConvention, stub: StubPeriodType, end_of_month: bool, terminal_convention: Option<BusinessDayConvention>, ) -> Result<Self, ScheduleError>
generate with stated stub dates: FpML’s
firstRegularPeriodStartDate and lastRegularPeriodEndDate.
The regular periods run between the regular anchors —
first_regular_start, else effective, and last_regular_end, else
termination — measured from one fixed anchor in the direction stub
names, exactly as generate measures them; any irregular period the
anchors leave sits where stub says. A stated first regular start adds
the front stub from effective to it, a stated last regular end the
back stub from it to termination: both give a double stub. Every
boundary is then adjusted as generate adjusts them, the last by
terminal_convention.
§Errors
ScheduleError::StubDateOutsideTerm for a stated date not strictly
inside (effective, termination), and
ScheduleError::StubDatesOutOfOrder for a first regular start not
before the last regular end.
pub fn from_dates(dates: Vec<NaiveDate>) -> Result<Self, ScheduleError>
pub fn from_dates(dates: Vec<NaiveDate>) -> Result<Self, ScheduleError>
A schedule from boundary dates stated verbatim.
No calendar, no roll convention and no business-day adjustment is
applied: a stated boundary has already had all three applied by whoever
stated it, and applying them again is exactly the double adjustment
Schedule::generate’s anchor contract exists to prevent. This is the
entry point for a schedule read off a confirm, or printed by another
library, rather than generated from conventions.
The result is the same value generate produces, so a
leg built on stated dates differs from one built on generated dates in
nothing but its dates.
A stated schedule is not refused for being unconventional — a boundary on a weekend, or periods of unequal length, are what stating dates is for. Only an ill-formed list is refused.
§Errors
ScheduleError::TooFewDates below two dates,
ScheduleError::OutOfOrder on a descending pair, and
ScheduleError::RepeatedDate on a repeat.
A repeat is refused rather than dropped, where
generate drops one. The two cases differ in kind: in
generation the collision is produced by the adjustment, so dropping one
answers a question the caller never asked, whereas a caller who wrote a
date twice made an error, and returning a schedule with fewer periods
than were enumerated is the silent wrong answer this entry point exists
to avoid.
pub fn periods(&self) -> Vec<(NaiveDate, NaiveDate)>
pub fn periods(&self) -> Vec<(NaiveDate, NaiveDate)>
The periods as (start, end) pairs. n boundaries yield n - 1.
To ask when each of those periods pays, see Schedule::paying, whose
impl block lives in cashflows::adjustments — it takes a
LagAdjustment, which belongs to the layer above this one, so the method
is written there to keep this layer from importing it.