openseries 1.6.0__py3-none-any.whl → 1.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
openseries/_risk.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from math import ceil
6
- from typing import Union, cast
6
+ from typing import TYPE_CHECKING, cast
7
7
 
8
8
  from numpy import (
9
9
  mean,
@@ -13,21 +13,21 @@ from numpy import (
13
13
  )
14
14
  from pandas import DataFrame, Series
15
15
 
16
- from openseries.types import LiteralQuantileInterp
16
+ if TYPE_CHECKING:
17
+ from .types import LiteralQuantileInterp # pragma: no cover
17
18
 
18
19
 
19
20
  def _cvar_down_calc(
20
- data: Union[DataFrame, Series[float], list[float]],
21
+ data: DataFrame | Series[float] | list[float],
21
22
  level: float = 0.95,
22
23
  ) -> float:
23
- """
24
- Calculate downside Conditional Value at Risk (CVaR).
24
+ """Calculate downside Conditional Value at Risk (CVaR).
25
25
 
26
26
  https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.
27
27
 
28
28
  Parameters
29
29
  ----------
30
- data: Union[DataFrame, Series[float], list[float]]
30
+ data: DataFrame | Series[float] | list[float]
31
31
  The data to perform the calculation over
32
32
  level: float, default: 0.95
33
33
  The sought CVaR level
@@ -48,19 +48,18 @@ def _cvar_down_calc(
48
48
 
49
49
 
50
50
  def _var_down_calc(
51
- data: Union[DataFrame, Series[float], list[float]],
51
+ data: DataFrame | Series[float] | list[float],
52
52
  level: float = 0.95,
53
53
  interpolation: LiteralQuantileInterp = "lower",
54
54
  ) -> float:
55
- """
56
- Calculate downside Value At Risk (VaR).
55
+ """Calculate downside Value At Risk (VaR).
57
56
 
58
57
  The equivalent of percentile.inc([...], 1-level) over returns in MS Excel
59
58
  https://www.investopedia.com/terms/v/var.asp.
60
59
 
61
60
  Parameters
62
61
  ----------
63
- data: Union[DataFrame, Series[float], list[float]]
62
+ data: DataFrame | Series[float] | list[float]
64
63
  The data to perform the calculation over
65
64
  level: float, default: 0.95
66
65
  The sought VaR level
openseries/datefixer.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import datetime as dt
6
- from typing import Optional, Union, cast
6
+ from typing import TYPE_CHECKING, cast
7
7
 
8
8
  from dateutil.relativedelta import relativedelta
9
9
  from holidays import (
@@ -15,25 +15,23 @@ from pandas import (
15
15
  DataFrame,
16
16
  DatetimeIndex,
17
17
  Index,
18
- Series,
19
18
  Timestamp,
20
19
  concat,
21
20
  date_range,
22
21
  )
23
22
  from pandas.tseries.offsets import CustomBusinessDay
24
- from pydantic import PositiveInt
25
23
 
26
- from openseries.types import (
27
- CountriesType,
28
- DateType,
29
- HolidayType,
30
- LiteralBizDayFreq,
31
- )
24
+ if TYPE_CHECKING:
25
+ from .types import ( # pragma: no cover
26
+ CountriesType,
27
+ DateType,
28
+ HolidayType,
29
+ LiteralBizDayFreq,
30
+ )
32
31
 
33
32
  __all__ = [
34
33
  "date_fix",
35
34
  "date_offset_foll",
36
- "do_resample_to_business_period_ends",
37
35
  "generate_calendar_date_range",
38
36
  "get_previous_business_day_before_today",
39
37
  "holiday_calendar",
@@ -45,10 +43,9 @@ def holiday_calendar(
45
43
  startyear: int,
46
44
  endyear: int,
47
45
  countries: CountriesType = "SE",
48
- custom_holidays: Optional[HolidayType] = None,
46
+ custom_holidays: HolidayType | None = None,
49
47
  ) -> busdaycalendar:
50
- """
51
- Generate a business calendar.
48
+ """Generate a business calendar.
52
49
 
53
50
  Parameters
54
51
  ----------
@@ -83,7 +80,7 @@ def holiday_calendar(
83
80
  country in list_supported_countries() for country in countries
84
81
  ):
85
82
  country: str
86
- countryholidays: list[Union[dt.date, str]] = []
83
+ countryholidays: list[dt.date | str] = []
87
84
  for i, country in enumerate(countries):
88
85
  staging = country_holidays(country=country, years=years)
89
86
  if i == 0 and custom_holidays is not None:
@@ -105,8 +102,7 @@ def holiday_calendar(
105
102
  def date_fix(
106
103
  fixerdate: DateType,
107
104
  ) -> dt.date:
108
- """
109
- Parse different date formats into datetime.date.
105
+ """Parse different date formats into datetime.date.
110
106
 
111
107
  Parameters
112
108
  ----------
@@ -125,9 +121,7 @@ def date_fix(
125
121
  return fixerdate
126
122
  if isinstance(fixerdate, datetime64):
127
123
  return (
128
- dt.datetime.strptime(str(fixerdate)[:10], "%Y-%m-%d")
129
- .astimezone()
130
- .date()
124
+ dt.datetime.strptime(str(fixerdate)[:10], "%Y-%m-%d").astimezone().date()
131
125
  )
132
126
  if isinstance(fixerdate, str):
133
127
  return dt.datetime.strptime(fixerdate, "%Y-%m-%d").astimezone().date()
@@ -141,13 +135,12 @@ def date_offset_foll(
141
135
  raw_date: DateType,
142
136
  months_offset: int = 12,
143
137
  countries: CountriesType = "SE",
144
- custom_holidays: Optional[HolidayType] = None,
138
+ custom_holidays: HolidayType | None = None,
145
139
  *,
146
140
  adjust: bool = False,
147
141
  following: bool = True,
148
142
  ) -> dt.date:
149
- """
150
- Offset dates according to a given calendar.
143
+ """Offset dates according to a given calendar.
151
144
 
152
145
  Parameters
153
146
  ----------
@@ -194,12 +187,11 @@ def date_offset_foll(
194
187
 
195
188
 
196
189
  def get_previous_business_day_before_today(
197
- today: Optional[dt.date] = None,
190
+ today: dt.date | None = None,
198
191
  countries: CountriesType = "SE",
199
- custom_holidays: Optional[HolidayType] = None,
192
+ custom_holidays: HolidayType | None = None,
200
193
  ) -> dt.date:
201
- """
202
- Bump date backwards to find the previous business day.
194
+ """Bump date backwards to find the previous business day.
203
195
 
204
196
  Parameters
205
197
  ----------
@@ -234,10 +226,9 @@ def offset_business_days(
234
226
  ddate: dt.date,
235
227
  days: int,
236
228
  countries: CountriesType = "SE",
237
- custom_holidays: Optional[HolidayType] = None,
229
+ custom_holidays: HolidayType | None = None,
238
230
  ) -> dt.date:
239
- """
240
- Bump date by business days.
231
+ """Bump date by business days.
241
232
 
242
233
  It first adjusts to a valid business day and then bumps with given
243
234
  number of business days from there.
@@ -313,18 +304,17 @@ def offset_business_days(
313
304
 
314
305
 
315
306
  def generate_calendar_date_range(
316
- trading_days: PositiveInt,
317
- start: Optional[dt.date] = None,
318
- end: Optional[dt.date] = None,
307
+ trading_days: int,
308
+ start: dt.date | None = None,
309
+ end: dt.date | None = None,
319
310
  countries: CountriesType = "SE",
320
311
  ) -> list[dt.date]:
321
- """
322
- Generate a list of business day calendar dates.
312
+ """Generate a list of business day calendar dates.
323
313
 
324
314
  Parameters
325
315
  ----------
326
- trading_days: PositiveInt
327
- Number of days to generate
316
+ trading_days: int
317
+ Number of days to generate. Must be greater than zero
328
318
  start: datetime.date, optional
329
319
  Date when the range starts
330
320
  end: datetime.date, optional
@@ -338,6 +328,10 @@ def generate_calendar_date_range(
338
328
  List of business day calendar dates
339
329
 
340
330
  """
331
+ if trading_days < 1:
332
+ msg = "Argument trading_days must be greater than zero."
333
+ raise ValueError(msg)
334
+
341
335
  if start and not end:
342
336
  tmp_range = date_range(
343
337
  start=start,
@@ -383,15 +377,13 @@ def generate_calendar_date_range(
383
377
  )
384
378
 
385
379
 
386
- def do_resample_to_business_period_ends(
380
+ # noinspection PyUnusedLocal
381
+ def _do_resample_to_business_period_ends(
387
382
  data: DataFrame,
388
- head: Series[float],
389
- tail: Series[float],
390
383
  freq: LiteralBizDayFreq,
391
384
  countries: CountriesType,
392
385
  ) -> DatetimeIndex:
393
- """
394
- Resample timeseries frequency to business calendar month end dates.
386
+ """Resample timeseries frequency to business calendar month end dates.
395
387
 
396
388
  Stubs left in place. Stubs will be aligned to the shortest stub.
397
389
 
@@ -399,10 +391,6 @@ def do_resample_to_business_period_ends(
399
391
  ----------
400
392
  data: pandas.DataFrame
401
393
  The timeseries data
402
- head: pandas:Series[float]
403
- Data point at maximum first date of all series
404
- tail: pandas:Series[float]
405
- Data point at minimum last date of all series
406
394
  freq: LiteralBizDayFreq
407
395
  The date offset string that sets the resampled frequency
408
396
  countries: CountriesType
@@ -415,25 +403,16 @@ def do_resample_to_business_period_ends(
415
403
  A date range aligned to business period ends
416
404
 
417
405
  """
418
- newhead = head.to_frame().T
419
- newtail = tail.to_frame().T
420
- data.index = DatetimeIndex(data.index)
421
- data = data.resample(rule=freq).last()
422
- data = data.drop(index=data.index[-1])
423
- data.index = Index(d.date() for d in DatetimeIndex(data.index))
424
-
425
- if newhead.index[0] not in data.index:
426
- # noinspection PyUnreachableCode
427
- data = concat([data, newhead])
428
-
429
- if newtail.index[0] not in data.index:
430
- # noinspection PyUnreachableCode
431
- data = concat([data, newtail])
406
+ copydata = data.copy()
407
+ copydata.index = DatetimeIndex(copydata.index)
408
+ copydata = copydata.resample(rule=freq).last()
409
+ copydata = copydata.drop(index=copydata.index[-1])
410
+ copydata.index = Index(d.date() for d in DatetimeIndex(copydata.index))
432
411
 
433
- data = data.sort_index()
412
+ copydata = concat([data.head(n=1), copydata, data.tail(n=1)]).sort_index()
434
413
 
435
414
  dates = DatetimeIndex(
436
- [data.index[0]]
415
+ [copydata.index[0]]
437
416
  + [
438
417
  date_offset_foll(
439
418
  dt.date(d.year, d.month, 1)
@@ -444,8 +423,8 @@ def do_resample_to_business_period_ends(
444
423
  adjust=True,
445
424
  following=False,
446
425
  )
447
- for d in data.index[1:-1]
426
+ for d in copydata.index[1:-1]
448
427
  ]
449
- + [data.index[-1]],
428
+ + [copydata.index[-1]],
450
429
  )
451
430
  return dates.drop_duplicates()