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};