RustQuant_time/date_rolling.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RustQuant: A Rust library for quantitative finance tools.
// Copyright (C) 2022-2024 https://github.com/avhz
// Dual licensed under Apache 2.0 and MIT.
// See:
// - LICENSE-APACHE.md
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
use super::{next_business_day, previous_business_day};
use crate::calendar::Calendar;
use std::fmt;
use time::Date;
/// Date rolling business day conventions.
///
/// From Wikipedia (<https://en.wikipedia.org/wiki/Date_rolling>):
/// """
/// In finance, date rolling occurs when a payment day or date used to
/// calculate accrued interest falls on a holiday, according to a given
/// business calendar. In this case the date is moved forward or backward in
/// time such that it falls in a business day, according with the
/// same business calendar.
/// """
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DateRollingConvention {
/// Actual: paid on the actual day, even if it is a non-business day.
Actual,
/// Following business day: the payment date is rolled to the next business day.
Following,
/// Modified following business day: the payment date is rolled to the
/// next business day, unless doing so
/// would cause the payment to be in the next calendar month,
/// in which case the payment date is rolled to the previous business day.
/// Many institutions have month-end accounting procedures that necessitate this.
ModifiedFollowing,
/// Previous business day: the payment date is rolled to the previous business day.
Preceding,
/// Modified previous business day: the payment date is rolled to the previous
/// business day, unless doing so would cause the payment to be in the previous
/// calendar month, in which case the payment date is rolled to the next
/// business day. Many institutions have month-end accounting procedures
/// that necessitate this.
ModifiedPreceding,
/// Modified Rolling business day: the payment date is rolled to the next
/// business day. The adjusted week date is used for the next coupon date.
/// So adjustments are cumulative (excluding month change).
ModifiedRolling,
}
/// Date roller trait for rolling coupon/payment dates according to a given convention.
pub trait DateRoller {
/// Roll the date according to the given convention.
fn roll_date(&self, date: Date, convention: &DateRollingConvention) -> Date;
/// Roll a list of dates according to the given convention.
fn roll_dates(&self, dates: &[Date], convention: &DateRollingConvention) -> Vec<Date>;
}
impl<C> DateRoller for C
where
C: Calendar,
{
#[rustfmt::skip]
fn roll_date(&self, date: Date, convention: &DateRollingConvention) -> Date {
match convention {
DateRollingConvention::Actual => DateRollingConvention::roll_date_actual(date, self),
DateRollingConvention::Following => DateRollingConvention::roll_date_following(date, self),
DateRollingConvention::ModifiedFollowing => DateRollingConvention::roll_date_modified_following(date, self),
DateRollingConvention::Preceding => DateRollingConvention::roll_date_preceding(date, self),
DateRollingConvention::ModifiedPreceding => DateRollingConvention::roll_date_modified_preceding(date, self),
DateRollingConvention::ModifiedRolling => DateRollingConvention::roll_date_modified_rolling(date, self),
}
}
fn roll_dates(&self, dates: &[Date], convention: &DateRollingConvention) -> Vec<Date> {
dates
.iter()
.map(|&date| self.roll_date(date, convention))
.collect()
}
}
impl Default for DateRollingConvention {
/// Default date rolling convention: Actual (paid on the actual day, even if it is a non-business day.)
fn default() -> Self {
DateRollingConvention::Actual
}
}
impl fmt::Display for DateRollingConvention {
#[rustfmt::skip]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Actual => write!(f, "Actual"),
Self::Following => write!(f, "Following"),
Self::ModifiedFollowing => write!(f, "Modified Following"),
Self::Preceding => write!(f, "Preceding"),
Self::ModifiedPreceding => write!(f, "Modified Preceding"),
Self::ModifiedRolling => write!(f, "Modified Rolling"),
}
}
}
impl DateRollingConvention {
/// Adjust (roll) the date according: Actual convention.
pub(crate) fn roll_date_actual<C: Calendar>(date: Date, _calendar: &C) -> Date {
date
}
/// Adjust (roll) the date according: Following convention.
pub(crate) fn roll_date_following<C: Calendar>(date: Date, calendar: &C) -> Date {
next_business_day(date, calendar)
}
/// Adjust (roll) the date according: Modified following convention.
pub(crate) fn roll_date_modified_following<C: Calendar>(date: Date, calendar: &C) -> Date {
let mut new_date = next_business_day(date, calendar);
if new_date.month() != date.month() {
new_date = previous_business_day(date, calendar);
}
new_date
}
/// Adjust (roll) the date according: Modified preceding convention.
pub(crate) fn roll_date_modified_preceding<C: Calendar>(date: Date, calendar: &C) -> Date {
let mut new_date = previous_business_day(date, calendar);
if new_date.month() != date.month() {
new_date = next_business_day(date, calendar);
}
new_date
}
/// Adjust (roll) the date according: Modified rolling convention.
pub(crate) fn roll_date_modified_rolling<C: Calendar>(date: Date, calendar: &C) -> Date {
let mut new_date = date;
while !calendar.is_business_day(new_date) {
new_date = new_date.next_day().unwrap();
}
new_date
}
/// Adjust (roll) the date according: Preceding convention.
pub(crate) fn roll_date_preceding<C: Calendar>(date: Date, calendar: &C) -> Date {
previous_business_day(date, calendar)
}
}