rateslib/scheduling/
mod.rs

1//! Create a business day [`Calendar`], instrument [`Schedule`] and perform financial date manipulation.
2//!
3//! The purpose of this module is to provide objects which are capable of replicating all of the
4//! complexities of financial instrument specification, including examples such as;
5//! - FX spot determination including all of the various currency pair rules.
6//! - Business day calendar combination for multi-currency derivatives.
7//! - Standard schedule generation including all of the accrual and payment [`Adjuster`] rules, like
8//!   *modified following*, CDS's unadjusted last period etc.
9//! - Inference for stub dates and monthly [`RollDay`] when utilising a UI which extends to users
10//!   being allowed to supply unknown or ambiguous parameters.
11//!
12//! # Calendars and Date Adjustment
13//!
14//! ## Calendars
15//!
16//! *Rateslib* provides three calendar types: [`Cal`], [`UnionCal`] and [`NamedCal`] and the container
17//! enum [`Calendar`]. These are based on simple holiday and weekend specification and union rules
18//! for combinations. Some common calendars are implemented directly by name, and can be combined
19//! with string parsing syntax.
20//!
21//! All calendars implement the [`DateRoll`] trait which provide simple date adjustment, which
22//! *rateslib* calls **rolling**. This involves moving forward or backward from non-business days
23//! (or non-settleable days) to specific **business days** or **settleable business days**.
24//!
25//! ### Example
26//! This example creates a business day calendar defining Saturday and Sunday weekends and a
27//! specific holiday (the Early May UK Bank Holiday). It uses a date rolling method to
28//! manipulate Saturday 29th April 2017 under the *'following'* and *'modified following'* rules.
29//! ```rust
30//! # use rateslib::scheduling::{Cal, ndt, DateRoll};
31//! let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
32//! assert_eq!(ndt(2017, 5, 2), cal.roll_forward_bus_day(&ndt(2017, 4, 29)));
33//! assert_eq!(ndt(2017, 4, 28), cal.roll_mod_forward_bus_day(&ndt(2017, 4, 29)));
34//! ```
35//!
36//! ## Date Adjustment
37//!
38//! Date adjustment allows for a more complicated set of rules than simple date rolling.
39//! The [`Adjuster`] is an enum which defines the implementation of all of these rules and may
40//! be extended in the future if more rules are required for more complex instruments. It
41//! implements the [`Adjustment`] trait requiring some object capable of performing [`DateRoll`] to
42//! define the operations.
43//!
44//! All [`Calendar`] types implement the [`CalendarAdjustment`] trait which permits date
45//! adjustment when an [`Adjuster`] is cross-provided.
46//!
47//! ### Example
48//! This example performs the complex rule of adjusting a given date forward by 5 calendar days
49//! and then rolling that result forward to the next settleable business day.
50//! ```rust
51//! # use rateslib::scheduling::{Cal, ndt, Adjuster, CalendarAdjustment};
52//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
53//! let adjuster = Adjuster::CalDaysLagSettle(5);
54//! assert_eq!(ndt(2017, 5, 2), cal.adjust(&ndt(2017, 4, 27), &adjuster));
55//! assert_eq!(ndt(2017, 5, 2), cal.adjust(&ndt(2017, 4, 24), &adjuster));
56//! ```
57//!
58//! # Schedules
59//!
60//! A [`Schedule`] is an ordered and patterned array of periods and dates.
61//!
62//! All [`Schedule`] objects in *rateslib* are centered about the definition of their [`Frequency`],
63//! which is an enum describing a regular period of time. Certain [`Frequency`] variants have
64//! additional information to fully parametrise them. For example a [`Frequency::BusDays`](Frequency) variant
65//! requires a [`Calendar`] to define its valid days, and a [`Frequency::Months`](Frequency) variant requires
66//! a [`RollDay`] to define the day in the month that separates its periods.
67//!
68//! The [`Frequency`] implements the [`Scheduling`] trait which allows periods and stubs to be
69//! defined, alluding to the documented definition of **regular** and **irregular** schedules as
70//! well as permitting the pattern of periods that can form a valid [`Schedule`].
71//!
72//! ### Example
73//! This example creates a new [`Schedule`] by inferring that it can be constructed as a **regular schedule**
74//! (one without stubs) if the [`RollDay`] is asserted to be the [`RollDay::IMM`](RollDay) variant.
75//! Without an *IMM* roll-day this schedule would be irregular with a short front stub.
76//! ```rust
77//! # use rateslib::scheduling::{Cal, ndt, Adjuster, Frequency, Schedule, RollDay, StubInference, Calendar};
78//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
79//! let schedule = Schedule::try_new_inferred(
80//!    ndt(2024, 3, 20),                        // effective
81//!    ndt(2025, 9, 17),                        // termination
82//!    Frequency::Months{number:3, roll: None}, // frequency
83//!    None,                                    // front_stub
84//!    None,                                    // back_stub
85//!    Calendar::Cal(cal),                      // calendar
86//!    Adjuster::ModifiedFollowing{},           // accrual_adjuster
87//!    Adjuster::BusDaysLagSettle(2),           // payment_adjuster
88//!    Adjuster::Actual{},                      // payment_adjuster2
89//!    None,                                    // payment_adjuster3
90//!    false,                                   // eom
91//!    Some(StubInference::ShortFront),         // stub_inference
92//! );
93//! # let schedule = schedule.unwrap();
94//! assert_eq!(schedule.frequency, Frequency::Months{number:3, roll: Some(RollDay::IMM())});
95//! assert!(schedule.is_regular());
96//! ```
97//! The next example creates a new [`Schedule`] by inferring that its `termination` is an adjusted
98//! end-of-month date, and therefore its [`RollDay`] is asserted to be the [`RollDay::Day(31)`](RollDay)
99//! variant, and its `utermination` is therefore 30th November and it infers a `ufront_stub` correctly
100//! as 31st May 2025.
101//! ```rust
102//! # use rateslib::scheduling::{Cal, ndt, Adjuster, Frequency, Schedule, RollDay, StubInference, Calendar};
103//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
104//! let schedule = Schedule::try_new_inferred(
105//!    ndt(2025, 4, 15),                        // effective
106//!    ndt(2025, 11, 28),                       // termination
107//!    Frequency::Months{number:3, roll: None}, // frequency
108//!    None,                                    // front_stub
109//!    None,                                    // back_stub
110//!    Calendar::Cal(cal),                      // calendar
111//!    Adjuster::ModifiedFollowing{},           // accrual_adjuster
112//!    Adjuster::BusDaysLagSettle(2),           // payment_adjuster
113//!    Adjuster::Actual{},                      // payment_adjuster2
114//!    None,                                    // payment_adjuster3
115//!    true,                                    // eom
116//!    Some(StubInference::ShortFront),         // stub_inference
117//! );
118//! # let schedule = schedule.unwrap();
119//! assert_eq!(schedule.frequency, Frequency::Months{number:3, roll: Some(RollDay::Day(31))});
120//! assert_eq!(schedule.utermination, ndt(2025, 11, 30));
121//! assert_eq!(schedule.ufront_stub, Some(ndt(2025, 5, 31)));
122//! ```
123
124mod calendars;
125mod convention;
126mod frequency;
127mod schedule;
128
129mod serde;
130
131pub(crate) mod py;
132
133pub use crate::scheduling::{
134    calendars::{
135        ndt, Adjuster, Adjustment, Cal, Calendar, CalendarAdjustment, DateRoll, NamedCal, UnionCal,
136    },
137    convention::Convention,
138    frequency::{Frequency, Imm, RollDay, Scheduling},
139    schedule::{Schedule, StubInference},
140};
141pub(crate) use crate::scheduling::{frequency::get_unadjusteds, py::PyAdjuster};