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//! The [supplementary material](https://www.amazon.com/dp/0995455562) discusses the algorithms,
13//! architecture and implementation of these objects.
14//!
15//! # Calendars and Date Adjustment
16//!
17//! ## Calendars
18//!
19//! *Rateslib* provides three calendar types: [`Cal`], [`UnionCal`] and [`NamedCal`] and the container
20//! enum [`Calendar`]. These are based on simple holiday and weekend specification and union rules
21//! for combinations. Some common calendars are implemented directly by name, and can be combined
22//! with string parsing syntax.
23//!
24//! All calendars implement the [`DateRoll`] trait which provide simple date adjustment, which
25//! *rateslib* calls **rolling**. This involves moving forward or backward from non-business days
26//! (or non-settleable days) to specific **business days** or **settleable business days**.
27//!
28//! ### Example
29//! This example creates a business day calendar defining Saturday and Sunday weekends and a
30//! specific holiday (the Early May UK Bank Holiday). It uses a date rolling method to
31//! manipulate Saturday 29th April 2017 under the *'following'* and *'modified following'* rules.
32//! ```rust
33//! # use rateslib::scheduling::{Cal, ndt, DateRoll};
34//! let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
35//! assert_eq!(ndt(2017, 5, 2), cal.roll_forward_bus_day(&ndt(2017, 4, 29)));
36//! assert_eq!(ndt(2017, 4, 28), cal.roll_mod_forward_bus_day(&ndt(2017, 4, 29)));
37//! ```
38//!
39//! ## Date Adjustment
40//!
41//! Date adjustment allows for a more complicated set of rules than simple date rolling.
42//! The [`Adjuster`] is an enum which defines the implementation of all of these rules and may
43//! be extended in the future if more rules are required for more complex instruments. It
44//! implements the [`Adjustment`] trait requiring some object capable of performing [`DateRoll`] to
45//! define the operations.
46//!
47//! All [`Calendar`] types implement the [`CalendarAdjustment`] trait which permits date
48//! adjustment when an [`Adjuster`] is cross-provided.
49//!
50//! ### Example
51//! This example performs the complex rule of adjusting a given date forward by 5 calendar days
52//! and then rolling that result forward to the next settleable business day.
53//! ```rust
54//! # use rateslib::scheduling::{Cal, ndt, Adjuster, CalendarAdjustment};
55//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
56//! let adjuster = Adjuster::CalDaysLagSettle(5);
57//! assert_eq!(ndt(2017, 5, 2), cal.adjust(&ndt(2017, 4, 27), &adjuster));
58//! assert_eq!(ndt(2017, 5, 2), cal.adjust(&ndt(2017, 4, 24), &adjuster));
59//! ```
60//!
61//! # Schedules
62//!
63//! A [`Schedule`] is an ordered and patterned array of periods and dates. Again, more details can
64//! be found in the [supplementary material](https://www.amazon.com/dp/0995455562).
65//!
66//! All [`Schedule`] objects in *rateslib* are centered about the definition of their [`Frequency`],
67//! which is an enum describing a regular period of time. Certain [`Frequency`] variants have
68//! additional information to fully parametrise them. For example a [`Frequency::BusDays`](Frequency) variant
69//! requires a [`Calendar`] to define its valid days, and a [`Frequency::Months`](Frequency) variant requires
70//! a [`RollDay`] to define the day in the month that separates its periods.
71//!
72//! The [`Frequency`] implements the [`Scheduling`] trait which allows periods and stubs to be
73//! defined, alluding to the documented definition of **regular** and **irregular** schedules as
74//! well as permitting the pattern of periods that can form a valid [`Schedule`].
75//!
76//! ### Example
77//! This example creates a new [`Schedule`] by inferring that it can be constructed as a **regular schedule**
78//! (one without stubs) if the [`RollDay`] is asserted to be the [`RollDay::IMM`](RollDay) variant.
79//! Without an *IMM* roll-day this schedule would be irregular with a short front stub.
80//! ```rust
81//! # use rateslib::scheduling::{Cal, ndt, Adjuster, Frequency, Schedule, RollDay, StubInference, Calendar};
82//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
83//! let schedule = Schedule::try_new_inferred(
84//!    ndt(2024, 3, 20),                        // effective
85//!    ndt(2025, 9, 17),                        // termination
86//!    Frequency::Months{number:3, roll: None}, // frequency
87//!    None,                                    // front_stub
88//!    None,                                    // back_stub
89//!    Calendar::Cal(cal),                      // calendar
90//!    Adjuster::ModifiedFollowing{},           // accrual_adjuster
91//!    Adjuster::BusDaysLagSettle(2),           // payment_adjuster
92//!    false,                                   // eom
93//!    Some(StubInference::ShortFront),         // stub_inference
94//! );
95//! # let schedule = schedule.unwrap();
96//! assert_eq!(schedule.frequency, Frequency::Months{number:3, roll: Some(RollDay::IMM())});
97//! assert!(schedule.is_regular());
98//! ```
99//! The next example creates a new [`Schedule`] by inferring that its `termination` is an adjusted
100//! end-of-month date, and therefore its [`RollDay`] is asserted to be the [`RollDay::Day(31)`](RollDay)
101//! variant, and its `utermination` is therefore 30th November and it infers a `ufront_stub` correctly
102//! as 31st May 2025.
103//! ```rust
104//! # use rateslib::scheduling::{Cal, ndt, Adjuster, Frequency, Schedule, RollDay, StubInference, Calendar};
105//! # let cal = Cal::new(vec![ndt(2017, 5, 1)], vec![5, 6]);
106//! let schedule = Schedule::try_new_inferred(
107//!    ndt(2025, 4, 15),                        // effective
108//!    ndt(2025, 11, 28),                       // termination
109//!    Frequency::Months{number:3, roll: None}, // frequency
110//!    None,                                    // front_stub
111//!    None,                                    // back_stub
112//!    Calendar::Cal(cal),                      // calendar
113//!    Adjuster::ModifiedFollowing{},           // accrual_adjuster
114//!    Adjuster::BusDaysLagSettle(2),           // payment_adjuster
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 dcfs;
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    dcfs::Convention,
138    frequency::{Frequency, Imm, RollDay, Scheduling},
139    schedule::{Schedule, StubInference},
140};
141pub(crate) use crate::scheduling::{
142    dcfs::_get_convention_str, frequency::get_unadjusteds, py::PyAdjuster,
143};