Coverage for rivapy / tools / _validators.py: 37%
100 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 14:36 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 14:36 +0000
1# -*- coding: utf-8 -*-
2import pandas as pd
3from enum import Enum
4from datetime import datetime, date
5from typing import List as _List, Tuple as _Tuple, Union as _Union
6from rivapy.tools.holidays_compat import HolidayBase as _HolidayBase, country_holidays as _CountryHoliday
7from holidays.utils import list_supported_countries as _list_supported_countries
9# from iso4217parse import \
10# by_alpha3 as _iso4217_by_alpha3, \
11# by_code_num as _iso4217_by_code_num, \
12# Currency as _Currency
15def _date_to_datetime(date_time: _Union[datetime, date]) -> date:
16 """
17 Converts a date to a datetime or leaves it unchanged if it is already of type datetime.
19 Args:
20 date_time (_Union[datetime, date]): Date(time) to be converted.
22 Returns:
23 date: (Potentially) Converted datetime.
24 """
25 if isinstance(date_time, datetime):
26 return date_time
27 elif isinstance(date_time, date):
28 return datetime.combine(date_time, datetime.min.time())
29 else:
30 raise TypeError("'" + str(date_time) + "' must be of type datetime or date!")
33def _check_positivity(value: float) -> float:
34 """
35 Checks if value is positive.
37 Args:
38 value (float): value to be checked for positivity.
40 Returns:
41 float: positive value
42 """
43 if value > 0.0:
44 return value
45 else:
46 raise ValueError(str(value) + " must be positive!")
49def _check_non_negativity(value: float) -> float:
50 """
51 Checks if value is non-negative.
53 Args:
54 value (float): value to be checked for non-negativity.
56 Returns:
57 float: non-negative value
58 """
59 if value < 0.0:
60 raise ValueError(str(value) + " must not be negative!")
61 else:
62 return value
65def _check_relation(less: float, more: float) -> _Tuple[float, float]:
66 """
67 Checks if the relative size of two floating numbers is as expected.
69 Args:
70 less (float): Number expected to be smaller.
71 more (float): Number expected to be bigger.
73 Returns:
74 Tuple[float, float]: Tuple of (two) ascending numbers.
75 """
76 if less < more:
77 return less, more
78 else:
79 raise Exception("'" + str(less) + "' must be smaller than '" + str(more) + "'.")
82def _is_start_before_end(start: date, end: date, strictly: bool = True) -> bool:
83 """
84 Checks if the start date is before (strictly = True) of not after (strictly = False) the end date, respectively.
86 Args:
87 start (date): Start date
88 end (date: End date
89 strictly (bool): Flag defining if the start date shall be strictly before or not after the end date,
90 respectively.
92 Returns:
93 bool: True if start date <(=) end date. False otherwise.
94 """
95 if start < end:
96 return True
97 elif start == end:
98 if strictly:
99 print("WARNING: '" + str(start) + "' must not be after '" + str(end) + "'!")
100 return False
101 else:
102 return True
103 else:
104 print("WARNING: '" + str(start) + "' must be earlier than '" + str(end) + "'!")
105 return False
108def _check_start_before_end(start: _Union[date, datetime], end: _Union[date, datetime]) -> _Tuple[date, date]:
109 """
110 Converts the two input dates from datetime to date format it necessary and checks if the first date is earlier
111 than the second one.
113 Args:
114 start (_Union[date, datetime]): Start date
115 end (_Union[date, datetime]): End date
117 Returns:
118 Tuple[date, date]: start date, end date
119 """
120 start_date = _date_to_datetime(start)
121 end_date = _date_to_datetime(end)
122 if start_date < end_date:
123 return start_date, end_date
124 else:
125 raise Exception("'" + str(start) + "' must be earlier than '" + str(end) + "'!")
128def _is_chronological(
129 start_date: date,
130 end_date: date,
131 dates: _List[date] = None,
132 strictly_start: bool = True,
133 strictly_between: bool = True,
134 strictly_end: bool = False,
135) -> bool:
136 """
137 Checks if a given set of dates fulfills the following requirements:
138 - start date <(=) end date
139 - start date <(=) dates[0] <(=) dates[1] <(=) ... <(=) dates[n] <(=) end date
141 Flags will control if the chronological order shall be fulfilled strictly or not.
143 Args:
144 start_date (date): First day of the interval the dates shall chronologically fall in.
145 end_date (date): Last day of the interval the dates shall chronologically fall in.
146 dates (List[date], optional): List of dates to be tested if they are ascending and between start and end date.
147 Defaults to None.
148 strictly_start(bool, optional): True, if start date must be strictly before following date, False otherwise.
149 Defaults to True.
150 strictly_between(bool, optional): True, if dates must be strictly monotonically ascending, False otherwise.
151 Defaults to True.
152 strictly_end(bool, optional): True, if end date must be strictly after previous date, False otherwise.
153 Defaults to False.
154 Returns:
155 bool: True, if all chronological requirements w.r.t. given dates are fulfilled. False, otherwise.
156 """
157 if dates is None:
158 return _is_start_before_end(start_date, end_date, (strictly_start & strictly_end))
159 else:
160 if ~_is_start_before_end(start_date, dates[0], strictly_start):
161 return False
163 for i in range(1, len(dates)):
164 if ~_is_start_before_end(dates[i], dates[i - 1], strictly_between):
165 return False
167 if ~_is_start_before_end(dates[-1], end_date, strictly_end):
168 return False
170 return True
173def _check_start_at_or_before_end(start: _Union[date, datetime], end: _Union[date, datetime]) -> _Tuple[date, date]:
174 """
175 Converts the two input dates from datetime to date format it necessary and checks if the first date is earlier
176 than the second one.
178 Args:
179 start (_Union[date, datetime]): Start date
180 end (_Union[date, datetime]): End date
182 Returns:
183 Tuple[date, date]: start date, end date
184 """
185 start_date = _date_to_datetime(start)
186 end_date = _date_to_datetime(end)
187 if start_date <= end_date:
188 return start_date, end_date
189 else:
190 raise Exception("'" + str(start) + "' must be earlier than '" + str(end) + "'!")
193def check_start_before_end(start: _Union[date, datetime], end: _Union[date, datetime]) -> _Tuple[date, date]:
194 """
195 Converts the two input dates from datetime to date format it necessary and checks if the first date is earlier
196 than the second one.
198 Args:
199 start (_Union[date, datetime]): Start date
200 end (_Union[date, datetime]): End date
202 Returns:
203 Tuple[date, date]: start date, end date
204 """
205 start_date = _date_to_datetime(start)
206 end_date = _date_to_datetime(end)
207 if start_date < end_date:
208 return start_date, end_date
209 else:
210 raise Exception("'" + str(start) + "' must be earlier than '" + str(end) + "'!")
213def _is_ascending_date_list(start_date: date, dates: _List[date], end_date: date, exclude_start: bool = True, exclude_end: bool = False) -> bool:
214 """
215 Checks if all specified dates, e.g. coupon payment dates, fall between start date and end date. Start and end date
216 are excluded dependent on the corresponding boolean flags. Moreover, the dates are verified to be ascending.
218 Args:
219 start_date (date): First day of the interval the dates shall foll in.
220 dates (List[date]): List of dates to be tested if they are ascending and between start and end date.
221 end_date (date): Last day of the interval the dates shall foll in.
222 exclude_start (bool, optional): True, if start date does not belong to the interval. False, otherwise.
223 Defaults to True.
224 exclude_end (bool, optional): True, if end date does not belong to the interval. False, otherwise.
225 Defaults to False.
227 Returns:
228 bool: True, if dates are ascending and fall between the interval given by start and end date. False, otherwise.
229 """
230 if dates[0] < start_date:
231 return False
232 elif exclude_start & (dates[0] == start_date):
233 return False
235 for i in range(1, len(dates)):
236 if dates[i] <= dates[i - 1]:
237 return False
239 if dates[-1] > end_date:
240 return False
241 elif exclude_end & (dates[-1] == end_date):
242 return False
244 return True
247def _string_to_calendar(calendar: _Union[_HolidayBase, str]) -> _HolidayBase:
248 """
249 Checks if calendar provided as _HolidayBase or string (of corresponding country), respectively, is known and
250 converts it if necessary into the HolidayBse format.
252 Args:
253 calendar (_Union[_HolidayBase, str]): Calendar provided as _HolidayBase or (country) string.
255 Returns:
256 _HolidayBase: (Potentially) converted calendar.
257 """
258 if isinstance(calendar, _HolidayBase):
259 return calendar
260 elif isinstance(calendar, str):
261 if calendar in _list_supported_countries():
262 return _CountryHoliday(calendar)
263 else:
264 raise Exception("Unknown calendar " + calendar + "'!")
265 else:
266 raise TypeError("The holiday calendar '" + str(calendar) + "' must be provided as HolidayBase or string!")
269def _validate_schedule(self):
270 if ~_is_start_before_end(self.__start_day, self.__end_day, True):
271 raise Exception("Chronological order mismatch!")
274def _check_pandas_index_for_datetime(dataframe: pd.DataFrame):
275 if isinstance(dataframe, pd.DataFrame):
276 if not isinstance(dataframe.index, pd.DatetimeIndex):
277 raise TypeError("The index of the DataFrame is not of type pd.DatetimeIndex!")
278 else:
279 raise TypeError(f"The argument is not of type pd.DataFrame!")
282def print_member_values(obj):
283 print(f"Inspecting instance of {type(obj).__name__}:\n")
284 for attr in dir(obj):
285 if attr.startswith("_"):
286 continue # Skip private and built-in attributes
287 value = getattr(obj, attr)
288 if not callable(value):
289 print(f"{attr}: {value}")