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

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 

8 

9# from iso4217parse import \ 

10# by_alpha3 as _iso4217_by_alpha3, \ 

11# by_code_num as _iso4217_by_code_num, \ 

12# Currency as _Currency 

13 

14 

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. 

18 

19 Args: 

20 date_time (_Union[datetime, date]): Date(time) to be converted. 

21 

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!") 

31 

32 

33def _check_positivity(value: float) -> float: 

34 """ 

35 Checks if value is positive. 

36 

37 Args: 

38 value (float): value to be checked for positivity. 

39 

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!") 

47 

48 

49def _check_non_negativity(value: float) -> float: 

50 """ 

51 Checks if value is non-negative. 

52 

53 Args: 

54 value (float): value to be checked for non-negativity. 

55 

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 

63 

64 

65def _check_relation(less: float, more: float) -> _Tuple[float, float]: 

66 """ 

67 Checks if the relative size of two floating numbers is as expected. 

68 

69 Args: 

70 less (float): Number expected to be smaller. 

71 more (float): Number expected to be bigger. 

72 

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) + "'.") 

80 

81 

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. 

85 

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. 

91 

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 

106 

107 

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. 

112 

113 Args: 

114 start (_Union[date, datetime]): Start date 

115 end (_Union[date, datetime]): End date 

116 

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) + "'!") 

126 

127 

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 

140 

141 Flags will control if the chronological order shall be fulfilled strictly or not. 

142 

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 

162 

163 for i in range(1, len(dates)): 

164 if ~_is_start_before_end(dates[i], dates[i - 1], strictly_between): 

165 return False 

166 

167 if ~_is_start_before_end(dates[-1], end_date, strictly_end): 

168 return False 

169 

170 return True 

171 

172 

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. 

177 

178 Args: 

179 start (_Union[date, datetime]): Start date 

180 end (_Union[date, datetime]): End date 

181 

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) + "'!") 

191 

192 

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. 

197 

198 Args: 

199 start (_Union[date, datetime]): Start date 

200 end (_Union[date, datetime]): End date 

201 

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) + "'!") 

211 

212 

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. 

217 

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. 

226 

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 

234 

235 for i in range(1, len(dates)): 

236 if dates[i] <= dates[i - 1]: 

237 return False 

238 

239 if dates[-1] > end_date: 

240 return False 

241 elif exclude_end & (dates[-1] == end_date): 

242 return False 

243 

244 return True 

245 

246 

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. 

251 

252 Args: 

253 calendar (_Union[_HolidayBase, str]): Calendar provided as _HolidayBase or (country) string. 

254 

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!") 

267 

268 

269def _validate_schedule(self): 

270 if ~_is_start_before_end(self.__start_day, self.__end_day, True): 

271 raise Exception("Chronological order mismatch!") 

272 

273 

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!") 

280 

281 

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}")