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/__init__.py +0 -2
- openseries/_common_model.py +261 -324
- openseries/_risk.py +9 -10
- openseries/datefixer.py +42 -63
- openseries/frame.py +125 -161
- openseries/load_plotly.py +8 -8
- openseries/portfoliotools.py +39 -35
- openseries/series.py +65 -93
- openseries/simulation.py +39 -50
- openseries/types.py +17 -24
- {openseries-1.6.0.dist-info → openseries-1.7.1.dist-info}/LICENSE.md +1 -1
- {openseries-1.6.0.dist-info → openseries-1.7.1.dist-info}/METADATA +4 -5
- openseries-1.7.1.dist-info/RECORD +16 -0
- openseries-1.6.0.dist-info/RECORD +0 -16
- {openseries-1.6.0.dist-info → openseries-1.7.1.dist-info}/WHEEL +0 -0
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
|
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
|
-
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from .types import LiteralQuantileInterp # pragma: no cover
|
17
18
|
|
18
19
|
|
19
20
|
def _cvar_down_calc(
|
20
|
-
data:
|
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:
|
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:
|
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:
|
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
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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:
|
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[
|
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:
|
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:
|
190
|
+
today: dt.date | None = None,
|
198
191
|
countries: CountriesType = "SE",
|
199
|
-
custom_holidays:
|
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:
|
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:
|
317
|
-
start:
|
318
|
-
end:
|
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:
|
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
|
-
|
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
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
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
|
-
|
412
|
+
copydata = concat([data.head(n=1), copydata, data.tail(n=1)]).sort_index()
|
434
413
|
|
435
414
|
dates = DatetimeIndex(
|
436
|
-
[
|
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
|
426
|
+
for d in copydata.index[1:-1]
|
448
427
|
]
|
449
|
-
+ [
|
428
|
+
+ [copydata.index[-1]],
|
450
429
|
)
|
451
430
|
return dates.drop_duplicates()
|