openseries 1.7.0__tar.gz → 1.7.2__tar.gz
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-1.7.0 → openseries-1.7.2}/PKG-INFO +2 -3
- {openseries-1.7.0 → openseries-1.7.2}/README.md +0 -1
- {openseries-1.7.0 → openseries-1.7.2}/openseries/__init__.py +0 -2
- {openseries-1.7.0 → openseries-1.7.2}/openseries/_common_model.py +29 -41
- {openseries-1.7.0 → openseries-1.7.2}/openseries/datefixer.py +17 -39
- {openseries-1.7.0 → openseries-1.7.2}/openseries/frame.py +48 -71
- {openseries-1.7.0 → openseries-1.7.2}/openseries/portfoliotools.py +17 -15
- {openseries-1.7.0 → openseries-1.7.2}/openseries/series.py +29 -37
- {openseries-1.7.0 → openseries-1.7.2}/openseries/simulation.py +14 -16
- {openseries-1.7.0 → openseries-1.7.2}/openseries/types.py +2 -6
- {openseries-1.7.0 → openseries-1.7.2}/pyproject.toml +11 -6
- {openseries-1.7.0 → openseries-1.7.2}/LICENSE.md +0 -0
- {openseries-1.7.0 → openseries-1.7.2}/openseries/_risk.py +0 -0
- {openseries-1.7.0 → openseries-1.7.2}/openseries/load_plotly.py +0 -0
- {openseries-1.7.0 → openseries-1.7.2}/openseries/plotly_captor_logo.json +0 -0
- {openseries-1.7.0 → openseries-1.7.2}/openseries/plotly_layouts.json +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: openseries
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.2
|
4
4
|
Summary: Tools for analyzing financial timeseries.
|
5
5
|
Home-page: https://github.com/CaptorAB/openseries
|
6
6
|
License: BSD-3-Clause
|
@@ -28,7 +28,7 @@ Requires-Dist: pyarrow (>=14.0.2,<18.0.0)
|
|
28
28
|
Requires-Dist: pydantic (>=2.5.2,<3.0.0)
|
29
29
|
Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
|
30
30
|
Requires-Dist: requests (>=2.20.0,<3.0.0)
|
31
|
-
Requires-Dist: scipy (>=1.11.4,<
|
31
|
+
Requires-Dist: scipy (>=1.11.4,<1.14.1)
|
32
32
|
Requires-Dist: statsmodels (>=0.14.0,<1.0.0)
|
33
33
|
Project-URL: Repository, https://github.com/CaptorAB/openseries
|
34
34
|
Description-Content-Type: text/markdown
|
@@ -261,7 +261,6 @@ make lint
|
|
261
261
|
|
262
262
|
| Method | Applies to | Description |
|
263
263
|
|:-------------------------|:-----------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
|
264
|
-
| `setup_class` | `OpenTimeSeries` | Class method that defines the `domestic` home currency and the `countries` home countries attributes. |
|
265
264
|
| `pandas_df` | `OpenTimeSeries` | Method to create the `tsdf` pandas.DataFrame from the `dates` and `values`. |
|
266
265
|
| `set_new_label` | `OpenTimeSeries` | Method to change the pandas.DataFrame column MultiIndex. |
|
267
266
|
| `running_adjustment` | `OpenTimeSeries` | Adjusts the series performance with a `float` factor. |
|
@@ -226,7 +226,6 @@ make lint
|
|
226
226
|
|
227
227
|
| Method | Applies to | Description |
|
228
228
|
|:-------------------------|:-----------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
|
229
|
-
| `setup_class` | `OpenTimeSeries` | Class method that defines the `domestic` home currency and the `countries` home countries attributes. |
|
230
229
|
| `pandas_df` | `OpenTimeSeries` | Method to create the `tsdf` pandas.DataFrame from the `dates` and `values`. |
|
231
230
|
| `set_new_label` | `OpenTimeSeries` | Method to change the pandas.DataFrame column MultiIndex. |
|
232
231
|
| `running_adjustment` | `OpenTimeSeries` | Adjusts the series performance with a `float` factor. |
|
@@ -3,7 +3,6 @@
|
|
3
3
|
from .datefixer import (
|
4
4
|
date_fix,
|
5
5
|
date_offset_foll,
|
6
|
-
do_resample_to_business_period_ends,
|
7
6
|
generate_calendar_date_range,
|
8
7
|
get_previous_business_day_before_today,
|
9
8
|
holiday_calendar,
|
@@ -31,7 +30,6 @@ __all__ = [
|
|
31
30
|
"simulate_portfolios",
|
32
31
|
"date_fix",
|
33
32
|
"date_offset_foll",
|
34
|
-
"do_resample_to_business_period_ends",
|
35
33
|
"generate_calendar_date_range",
|
36
34
|
"get_previous_business_day_before_today",
|
37
35
|
"holiday_calendar",
|
@@ -447,7 +447,7 @@ class _CommonModel(BaseModel):
|
|
447
447
|
interpolation: LiteralQuantileInterp = "lower"
|
448
448
|
return self.vol_from_var_func(level=level, interpolation=interpolation)
|
449
449
|
|
450
|
-
def calc_range(
|
450
|
+
def calc_range(
|
451
451
|
self: Self,
|
452
452
|
months_offset: int | None = None,
|
453
453
|
from_dt: dt.date | None = None,
|
@@ -471,42 +471,36 @@ class _CommonModel(BaseModel):
|
|
471
471
|
Start and end date of the chosen date range
|
472
472
|
|
473
473
|
"""
|
474
|
-
earlier, later = self.
|
475
|
-
if
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
474
|
+
earlier, later = self.first_idx, self.last_idx
|
475
|
+
if months_offset is not None:
|
476
|
+
earlier = date_offset_foll(
|
477
|
+
raw_date=self.last_idx,
|
478
|
+
months_offset=-months_offset,
|
479
|
+
adjust=False,
|
480
|
+
following=True,
|
481
|
+
)
|
482
|
+
if earlier < self.first_idx:
|
483
|
+
msg = (
|
484
|
+
"Argument months_offset implies start"
|
485
|
+
"date before first date in series."
|
482
486
|
)
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
later = self.tsdf.index[-1]
|
489
|
-
elif from_dt is not None and to_dt is None:
|
490
|
-
if from_dt < self.tsdf.index[0]:
|
487
|
+
raise ValueError(msg)
|
488
|
+
later = self.last_idx
|
489
|
+
else:
|
490
|
+
if from_dt is not None:
|
491
|
+
if from_dt < self.first_idx:
|
491
492
|
msg = "Given from_dt date < series start"
|
492
493
|
raise ValueError(msg)
|
493
|
-
earlier
|
494
|
-
|
495
|
-
if to_dt > self.
|
494
|
+
earlier = from_dt
|
495
|
+
if to_dt is not None:
|
496
|
+
if to_dt > self.last_idx:
|
496
497
|
msg = "Given to_dt date > series end"
|
497
498
|
raise ValueError(msg)
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
msg,
|
504
|
-
)
|
505
|
-
earlier, later = from_dt, to_dt
|
506
|
-
while earlier not in self.tsdf.index.tolist():
|
507
|
-
earlier -= dt.timedelta(days=1)
|
508
|
-
while later not in self.tsdf.index.tolist():
|
509
|
-
later += dt.timedelta(days=1)
|
499
|
+
later = to_dt
|
500
|
+
while earlier not in self.tsdf.index:
|
501
|
+
earlier -= dt.timedelta(days=1)
|
502
|
+
while later not in self.tsdf.index:
|
503
|
+
later += dt.timedelta(days=1)
|
510
504
|
|
511
505
|
return earlier, later
|
512
506
|
|
@@ -789,9 +783,7 @@ class _CommonModel(BaseModel):
|
|
789
783
|
if labels:
|
790
784
|
if len(labels) != self.tsdf.shape[1]:
|
791
785
|
msg = "Must provide same number of labels as items in frame."
|
792
|
-
raise ValueError(
|
793
|
-
msg,
|
794
|
-
)
|
786
|
+
raise ValueError(msg)
|
795
787
|
else:
|
796
788
|
labels = list(self.tsdf.columns.get_level_values(0))
|
797
789
|
|
@@ -906,9 +898,7 @@ class _CommonModel(BaseModel):
|
|
906
898
|
if labels:
|
907
899
|
if len(labels) != self.tsdf.shape[1]:
|
908
900
|
msg = "Must provide same number of labels as items in frame."
|
909
|
-
raise ValueError(
|
910
|
-
msg,
|
911
|
-
)
|
901
|
+
raise ValueError(msg)
|
912
902
|
else:
|
913
903
|
labels = list(self.tsdf.columns.get_level_values(0))
|
914
904
|
|
@@ -1936,9 +1926,7 @@ class _CommonModel(BaseModel):
|
|
1936
1926
|
"Simple return cannot be calculated due to "
|
1937
1927
|
f"an initial value being zero. ({self.tsdf.head(3)})"
|
1938
1928
|
)
|
1939
|
-
raise ValueError(
|
1940
|
-
msg,
|
1941
|
-
)
|
1929
|
+
raise ValueError(msg)
|
1942
1930
|
|
1943
1931
|
result = self.tsdf.loc[later] / self.tsdf.loc[earlier] - 1
|
1944
1932
|
|
@@ -15,15 +15,14 @@ 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
23
|
|
25
|
-
if TYPE_CHECKING:
|
26
|
-
from .types import (
|
24
|
+
if TYPE_CHECKING:
|
25
|
+
from .types import ( # pragma: no cover
|
27
26
|
CountriesType,
|
28
27
|
DateType,
|
29
28
|
HolidayType,
|
@@ -33,7 +32,6 @@ if TYPE_CHECKING: # pragma: no cover
|
|
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",
|
@@ -94,9 +92,7 @@ def holiday_calendar(
|
|
94
92
|
"Argument countries must be a string country code or "
|
95
93
|
"a list of string country codes according to ISO 3166-1 alpha-2."
|
96
94
|
)
|
97
|
-
raise ValueError(
|
98
|
-
msg,
|
99
|
-
)
|
95
|
+
raise ValueError(msg)
|
100
96
|
|
101
97
|
return busdaycalendar(holidays=hols)
|
102
98
|
|
@@ -117,6 +113,7 @@ def date_fix(
|
|
117
113
|
Parsed date
|
118
114
|
|
119
115
|
"""
|
116
|
+
msg = f"Unknown date format {fixerdate!s} of type {type(fixerdate)!s} encountered"
|
120
117
|
if isinstance(fixerdate, (Timestamp, dt.datetime)):
|
121
118
|
return fixerdate.date()
|
122
119
|
if isinstance(fixerdate, dt.date):
|
@@ -127,10 +124,7 @@ def date_fix(
|
|
127
124
|
)
|
128
125
|
if isinstance(fixerdate, str):
|
129
126
|
return dt.datetime.strptime(fixerdate, "%Y-%m-%d").astimezone().date()
|
130
|
-
|
131
|
-
raise TypeError(
|
132
|
-
msg,
|
133
|
-
)
|
127
|
+
raise TypeError(msg)
|
134
128
|
|
135
129
|
|
136
130
|
def date_offset_foll(
|
@@ -374,15 +368,12 @@ def generate_calendar_date_range(
|
|
374
368
|
"Provide one of start or end date, but not both. "
|
375
369
|
"Date range is inferred from number of trading days."
|
376
370
|
)
|
377
|
-
raise ValueError(
|
378
|
-
msg,
|
379
|
-
)
|
371
|
+
raise ValueError(msg)
|
380
372
|
|
381
373
|
|
382
|
-
|
374
|
+
# noinspection PyUnusedLocal
|
375
|
+
def _do_resample_to_business_period_ends(
|
383
376
|
data: DataFrame,
|
384
|
-
head: Series[float],
|
385
|
-
tail: Series[float],
|
386
377
|
freq: LiteralBizDayFreq,
|
387
378
|
countries: CountriesType,
|
388
379
|
) -> DatetimeIndex:
|
@@ -394,10 +385,6 @@ def do_resample_to_business_period_ends(
|
|
394
385
|
----------
|
395
386
|
data: pandas.DataFrame
|
396
387
|
The timeseries data
|
397
|
-
head: pandas:Series[float]
|
398
|
-
Data point at maximum first date of all series
|
399
|
-
tail: pandas:Series[float]
|
400
|
-
Data point at minimum last date of all series
|
401
388
|
freq: LiteralBizDayFreq
|
402
389
|
The date offset string that sets the resampled frequency
|
403
390
|
countries: CountriesType
|
@@ -410,25 +397,16 @@ def do_resample_to_business_period_ends(
|
|
410
397
|
A date range aligned to business period ends
|
411
398
|
|
412
399
|
"""
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
data.index = Index(d.date() for d in DatetimeIndex(data.index))
|
419
|
-
|
420
|
-
if newhead.index[0] not in data.index:
|
421
|
-
# noinspection PyUnreachableCode
|
422
|
-
data = concat([data, newhead])
|
423
|
-
|
424
|
-
if newtail.index[0] not in data.index:
|
425
|
-
# noinspection PyUnreachableCode
|
426
|
-
data = concat([data, newtail])
|
400
|
+
copydata = data.copy()
|
401
|
+
copydata.index = DatetimeIndex(copydata.index)
|
402
|
+
copydata = copydata.resample(rule=freq).last()
|
403
|
+
copydata = copydata.drop(index=copydata.index[-1])
|
404
|
+
copydata.index = Index(d.date() for d in DatetimeIndex(copydata.index))
|
427
405
|
|
428
|
-
|
406
|
+
copydata = concat([data.head(n=1), copydata, data.tail(n=1)]).sort_index()
|
429
407
|
|
430
408
|
dates = DatetimeIndex(
|
431
|
-
[
|
409
|
+
[copydata.index[0]]
|
432
410
|
+ [
|
433
411
|
date_offset_foll(
|
434
412
|
dt.date(d.year, d.month, 1)
|
@@ -439,8 +417,8 @@ def do_resample_to_business_period_ends(
|
|
439
417
|
adjust=True,
|
440
418
|
following=False,
|
441
419
|
)
|
442
|
-
for d in
|
420
|
+
for d in copydata.index[1:-1]
|
443
421
|
]
|
444
|
-
+ [
|
422
|
+
+ [copydata.index[-1]],
|
445
423
|
)
|
446
424
|
return dates.drop_duplicates()
|
@@ -13,7 +13,6 @@ if TYPE_CHECKING:
|
|
13
13
|
|
14
14
|
import statsmodels.api as sm # type: ignore[import-untyped,unused-ignore]
|
15
15
|
from numpy import (
|
16
|
-
array,
|
17
16
|
cov,
|
18
17
|
cumprod,
|
19
18
|
divide,
|
@@ -43,7 +42,7 @@ from statsmodels.regression.linear_model import ( # type: ignore[import-untyped
|
|
43
42
|
from typing_extensions import Self
|
44
43
|
|
45
44
|
from ._common_model import _CommonModel
|
46
|
-
from .datefixer import
|
45
|
+
from .datefixer import _do_resample_to_business_period_ends
|
47
46
|
from .series import OpenTimeSeries
|
48
47
|
from .types import (
|
49
48
|
CountriesType,
|
@@ -443,21 +442,18 @@ class OpenFrame(_CommonModel):
|
|
443
442
|
An OpenFrame object
|
444
443
|
|
445
444
|
"""
|
446
|
-
head: Series[float] = self.tsdf.loc[self.first_indices.max()].copy()
|
447
|
-
tail: Series[float] = self.tsdf.loc[self.last_indices.min()].copy()
|
448
|
-
dates = do_resample_to_business_period_ends(
|
449
|
-
data=self.tsdf,
|
450
|
-
head=head,
|
451
|
-
tail=tail,
|
452
|
-
freq=freq,
|
453
|
-
countries=countries,
|
454
|
-
)
|
455
|
-
self.tsdf = self.tsdf.reindex([deyt.date() for deyt in dates], method=method)
|
456
445
|
for xerie in self.constituents:
|
446
|
+
dates = _do_resample_to_business_period_ends(
|
447
|
+
data=xerie.tsdf,
|
448
|
+
freq=freq,
|
449
|
+
countries=countries,
|
450
|
+
)
|
457
451
|
xerie.tsdf = xerie.tsdf.reindex(
|
458
|
-
[deyt.date() for deyt in dates],
|
459
|
-
method=method,
|
452
|
+
[deyt.date() for deyt in dates], method=method,
|
460
453
|
)
|
454
|
+
|
455
|
+
self._set_tsdf()
|
456
|
+
|
461
457
|
return self
|
462
458
|
|
463
459
|
def ewma_risk(
|
@@ -780,6 +776,7 @@ class OpenFrame(_CommonModel):
|
|
780
776
|
earlier, later = self.calc_range(months_from_last, from_date, to_date)
|
781
777
|
fraction = (later - earlier).days / 365.25
|
782
778
|
|
779
|
+
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
783
780
|
if isinstance(base_column, tuple):
|
784
781
|
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
785
782
|
:,
|
@@ -801,10 +798,7 @@ class OpenFrame(_CommonModel):
|
|
801
798
|
].name
|
802
799
|
short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[0]
|
803
800
|
else:
|
804
|
-
msg
|
805
|
-
raise TypeError(
|
806
|
-
msg,
|
807
|
-
)
|
801
|
+
raise TypeError(msg)
|
808
802
|
|
809
803
|
if periods_in_a_year_fixed:
|
810
804
|
time_factor = float(periods_in_a_year_fixed)
|
@@ -873,6 +867,7 @@ class OpenFrame(_CommonModel):
|
|
873
867
|
earlier, later = self.calc_range(months_from_last, from_date, to_date)
|
874
868
|
fraction = (later - earlier).days / 365.25
|
875
869
|
|
870
|
+
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
876
871
|
if isinstance(base_column, tuple):
|
877
872
|
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
878
873
|
:,
|
@@ -894,10 +889,7 @@ class OpenFrame(_CommonModel):
|
|
894
889
|
].name
|
895
890
|
short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[0]
|
896
891
|
else:
|
897
|
-
msg
|
898
|
-
raise TypeError(
|
899
|
-
msg,
|
900
|
-
)
|
892
|
+
raise TypeError(msg)
|
901
893
|
|
902
894
|
if periods_in_a_year_fixed:
|
903
895
|
time_factor = float(periods_in_a_year_fixed)
|
@@ -977,6 +969,7 @@ class OpenFrame(_CommonModel):
|
|
977
969
|
earlier, later = self.calc_range(months_from_last, from_date, to_date)
|
978
970
|
fraction = (later - earlier).days / 365.25
|
979
971
|
|
972
|
+
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
980
973
|
if isinstance(base_column, tuple):
|
981
974
|
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
982
975
|
:,
|
@@ -998,10 +991,7 @@ class OpenFrame(_CommonModel):
|
|
998
991
|
].name
|
999
992
|
short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[0]
|
1000
993
|
else:
|
1001
|
-
msg
|
1002
|
-
raise TypeError(
|
1003
|
-
msg,
|
1004
|
-
)
|
994
|
+
raise TypeError(msg)
|
1005
995
|
|
1006
996
|
if periods_in_a_year_fixed:
|
1007
997
|
time_factor = float(periods_in_a_year_fixed)
|
@@ -1017,6 +1007,7 @@ class OpenFrame(_CommonModel):
|
|
1017
1007
|
:,
|
1018
1008
|
item,
|
1019
1009
|
]
|
1010
|
+
msg = "ratio must be one of 'up', 'down' or 'both'."
|
1020
1011
|
if ratio == "up":
|
1021
1012
|
uparray = (
|
1022
1013
|
longdf.pct_change(fill_method=None)[
|
@@ -1111,6 +1102,8 @@ class OpenFrame(_CommonModel):
|
|
1111
1102
|
ratios.append(
|
1112
1103
|
(up_rtrn / up_idx_return) / (down_return / down_idx_return),
|
1113
1104
|
)
|
1105
|
+
else:
|
1106
|
+
raise ValueError(msg)
|
1114
1107
|
|
1115
1108
|
if ratio == "up":
|
1116
1109
|
resultname = f"Up Capture Ratios vs {short_label}"
|
@@ -1156,25 +1149,23 @@ class OpenFrame(_CommonModel):
|
|
1156
1149
|
x_value == ValueType.RTRN
|
1157
1150
|
for x_value in self.tsdf.columns.get_level_values(1).to_numpy()
|
1158
1151
|
):
|
1152
|
+
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1159
1153
|
if isinstance(asset, tuple):
|
1160
1154
|
y_value = self.tsdf.loc[:, asset]
|
1161
1155
|
elif isinstance(asset, int):
|
1162
1156
|
y_value = self.tsdf.iloc[:, asset]
|
1163
1157
|
else:
|
1164
|
-
msg
|
1165
|
-
|
1166
|
-
|
1167
|
-
)
|
1158
|
+
raise TypeError(msg)
|
1159
|
+
|
1160
|
+
msg = "market should be a tuple[str, ValueType] or an integer."
|
1168
1161
|
if isinstance(market, tuple):
|
1169
1162
|
x_value = self.tsdf.loc[:, market]
|
1170
1163
|
elif isinstance(market, int):
|
1171
1164
|
x_value = self.tsdf.iloc[:, market]
|
1172
1165
|
else:
|
1173
|
-
msg
|
1174
|
-
raise TypeError(
|
1175
|
-
msg,
|
1176
|
-
)
|
1166
|
+
raise TypeError(msg)
|
1177
1167
|
else:
|
1168
|
+
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1178
1169
|
if isinstance(asset, tuple):
|
1179
1170
|
y_value = log(
|
1180
1171
|
self.tsdf.loc[:, asset] / self.tsdf.loc[:, asset].iloc[0],
|
@@ -1184,10 +1175,9 @@ class OpenFrame(_CommonModel):
|
|
1184
1175
|
self.tsdf.iloc[:, asset] / cast(float, self.tsdf.iloc[0, asset]),
|
1185
1176
|
)
|
1186
1177
|
else:
|
1187
|
-
msg
|
1188
|
-
|
1189
|
-
|
1190
|
-
)
|
1178
|
+
raise TypeError(msg)
|
1179
|
+
|
1180
|
+
msg = "market should be a tuple[str, ValueType] or an integer."
|
1191
1181
|
if isinstance(market, tuple):
|
1192
1182
|
x_value = log(
|
1193
1183
|
self.tsdf.loc[:, market] / self.tsdf.loc[:, market].iloc[0],
|
@@ -1197,10 +1187,7 @@ class OpenFrame(_CommonModel):
|
|
1197
1187
|
self.tsdf.iloc[:, market] / cast(float, self.tsdf.iloc[0, market]),
|
1198
1188
|
)
|
1199
1189
|
else:
|
1200
|
-
msg
|
1201
|
-
raise TypeError(
|
1202
|
-
msg,
|
1203
|
-
)
|
1190
|
+
raise TypeError(msg)
|
1204
1191
|
|
1205
1192
|
covariance = cov(y_value, x_value, ddof=dlta_degr_freedms)
|
1206
1193
|
beta = covariance[0, 1] / covariance[1, 1]
|
@@ -1241,6 +1228,7 @@ class OpenFrame(_CommonModel):
|
|
1241
1228
|
The Statsmodels regression output
|
1242
1229
|
|
1243
1230
|
"""
|
1231
|
+
msg = "y_column should be a tuple[str, ValueType] or an integer."
|
1244
1232
|
if isinstance(y_column, tuple):
|
1245
1233
|
y_value = self.tsdf.loc[:, y_column]
|
1246
1234
|
y_label = cast(
|
@@ -1251,11 +1239,9 @@ class OpenFrame(_CommonModel):
|
|
1251
1239
|
y_value = self.tsdf.iloc[:, y_column]
|
1252
1240
|
y_label = cast(tuple[str, str], self.tsdf.iloc[:, y_column].name)[0]
|
1253
1241
|
else:
|
1254
|
-
msg
|
1255
|
-
raise TypeError(
|
1256
|
-
msg,
|
1257
|
-
)
|
1242
|
+
raise TypeError(msg)
|
1258
1243
|
|
1244
|
+
msg = "x_column should be a tuple[str, ValueType] or an integer."
|
1259
1245
|
if isinstance(x_column, tuple):
|
1260
1246
|
x_value = self.tsdf.loc[:, x_column]
|
1261
1247
|
x_label = cast(
|
@@ -1266,10 +1252,7 @@ class OpenFrame(_CommonModel):
|
|
1266
1252
|
x_value = self.tsdf.iloc[:, x_column]
|
1267
1253
|
x_label = cast(tuple[str, str], self.tsdf.iloc[:, x_column].name)[0]
|
1268
1254
|
else:
|
1269
|
-
msg
|
1270
|
-
raise TypeError(
|
1271
|
-
msg,
|
1272
|
-
)
|
1255
|
+
raise TypeError(msg)
|
1273
1256
|
|
1274
1257
|
results = sm.OLS(y_value, x_value).fit(method=method, cov_type=cov_type)
|
1275
1258
|
if fitted_series:
|
@@ -1315,6 +1298,7 @@ class OpenFrame(_CommonModel):
|
|
1315
1298
|
x == ValueType.RTRN
|
1316
1299
|
for x in self.tsdf.columns.get_level_values(1).to_numpy()
|
1317
1300
|
):
|
1301
|
+
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1318
1302
|
if isinstance(asset, tuple):
|
1319
1303
|
asset_log = self.tsdf.loc[:, asset]
|
1320
1304
|
asset_cagr = asset_log.mean()
|
@@ -1322,10 +1306,9 @@ class OpenFrame(_CommonModel):
|
|
1322
1306
|
asset_log = self.tsdf.iloc[:, asset]
|
1323
1307
|
asset_cagr = asset_log.mean()
|
1324
1308
|
else:
|
1325
|
-
msg
|
1326
|
-
|
1327
|
-
|
1328
|
-
)
|
1309
|
+
raise TypeError(msg)
|
1310
|
+
|
1311
|
+
msg = "market should be a tuple[str, ValueType] or an integer."
|
1329
1312
|
if isinstance(market, tuple):
|
1330
1313
|
market_log = self.tsdf.loc[:, market]
|
1331
1314
|
market_cagr = market_log.mean()
|
@@ -1333,11 +1316,9 @@ class OpenFrame(_CommonModel):
|
|
1333
1316
|
market_log = self.tsdf.iloc[:, market]
|
1334
1317
|
market_cagr = market_log.mean()
|
1335
1318
|
else:
|
1336
|
-
msg
|
1337
|
-
raise TypeError(
|
1338
|
-
msg,
|
1339
|
-
)
|
1319
|
+
raise TypeError(msg)
|
1340
1320
|
else:
|
1321
|
+
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1341
1322
|
if isinstance(asset, tuple):
|
1342
1323
|
asset_log = log(
|
1343
1324
|
self.tsdf.loc[:, asset] / self.tsdf.loc[:, asset].iloc[0],
|
@@ -1369,10 +1350,9 @@ class OpenFrame(_CommonModel):
|
|
1369
1350
|
- 1
|
1370
1351
|
)
|
1371
1352
|
else:
|
1372
|
-
msg
|
1373
|
-
|
1374
|
-
|
1375
|
-
)
|
1353
|
+
raise TypeError(msg)
|
1354
|
+
|
1355
|
+
msg = "market should be a tuple[str, ValueType] or an integer."
|
1376
1356
|
if isinstance(market, tuple):
|
1377
1357
|
market_log = log(
|
1378
1358
|
self.tsdf.loc[:, market] / self.tsdf.loc[:, market].iloc[0],
|
@@ -1404,10 +1384,7 @@ class OpenFrame(_CommonModel):
|
|
1404
1384
|
- 1
|
1405
1385
|
)
|
1406
1386
|
else:
|
1407
|
-
msg
|
1408
|
-
raise TypeError(
|
1409
|
-
msg,
|
1410
|
-
)
|
1387
|
+
raise TypeError(msg)
|
1411
1388
|
|
1412
1389
|
covariance = cov(asset_log, market_log, ddof=dlta_degr_freedms)
|
1413
1390
|
beta = covariance[0, 1] / covariance[1, 1]
|
@@ -1439,9 +1416,7 @@ class OpenFrame(_CommonModel):
|
|
1439
1416
|
"OpenFrame weights property must be provided "
|
1440
1417
|
"to run the make_portfolio method."
|
1441
1418
|
)
|
1442
|
-
raise ValueError(
|
1443
|
-
msg,
|
1444
|
-
)
|
1419
|
+
raise ValueError(msg)
|
1445
1420
|
dframe = self.tsdf.copy()
|
1446
1421
|
if not any(
|
1447
1422
|
x == ValueType.RTRN
|
@@ -1449,6 +1424,8 @@ class OpenFrame(_CommonModel):
|
|
1449
1424
|
):
|
1450
1425
|
dframe = dframe.pct_change(fill_method=None)
|
1451
1426
|
dframe.iloc[0] = 0
|
1427
|
+
|
1428
|
+
msg = "Weight strategy not implemented"
|
1452
1429
|
if weight_strat:
|
1453
1430
|
if weight_strat == "eq_weights":
|
1454
1431
|
self.weights = [1.0 / self.item_count] * self.item_count
|
@@ -1457,10 +1434,10 @@ class OpenFrame(_CommonModel):
|
|
1457
1434
|
vol[isinf(vol)] = nan
|
1458
1435
|
self.weights = list(divide(vol, vol.sum()))
|
1459
1436
|
else:
|
1460
|
-
msg = "Weight strategy not implemented"
|
1461
1437
|
raise NotImplementedError(msg)
|
1438
|
+
|
1462
1439
|
return DataFrame(
|
1463
|
-
data=dframe
|
1440
|
+
data=(dframe @ self.weights).add(1.0).cumprod(),
|
1464
1441
|
index=self.tsdf.index,
|
1465
1442
|
columns=[[name], [ValueType.PRICE]],
|
1466
1443
|
dtype="float64",
|
@@ -10,9 +10,9 @@ from typing import TYPE_CHECKING, Callable, cast
|
|
10
10
|
from numpy import (
|
11
11
|
append,
|
12
12
|
array,
|
13
|
-
dot,
|
14
13
|
float64,
|
15
14
|
inf,
|
15
|
+
isnan,
|
16
16
|
linspace,
|
17
17
|
nan,
|
18
18
|
sqrt,
|
@@ -105,11 +105,8 @@ def simulate_portfolios(
|
|
105
105
|
weights = weights / npsum(weights)
|
106
106
|
all_weights[x, :] = weights
|
107
107
|
|
108
|
-
vol_arr[x] = sqrt(
|
109
|
-
|
110
|
-
weights.T,
|
111
|
-
dot(log_ret.cov() * simframe.periods_in_a_year, weights),
|
112
|
-
),
|
108
|
+
vol_arr[x] = sqrt(weights.T @
|
109
|
+
(log_ret.cov() * simframe.periods_in_a_year @ weights),
|
113
110
|
)
|
114
111
|
|
115
112
|
ret_arr[x] = npsum(log_ret.mean() * weights * simframe.periods_in_a_year)
|
@@ -182,10 +179,11 @@ def efficient_frontier( # noqa: C901
|
|
182
179
|
simulated = simulate_portfolios(simframe=copi, num_ports=num_ports, seed=seed)
|
183
180
|
|
184
181
|
frontier_min = simulated.loc[simulated["stdev"].idxmin()]["ret"]
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
182
|
+
|
183
|
+
arithmetic_means = array(log_ret.mean() * copi.periods_in_a_year)
|
184
|
+
cleaned_arithmetic_means = arithmetic_means[~isnan(arithmetic_means)]
|
185
|
+
|
186
|
+
frontier_max = cleaned_arithmetic_means.max()
|
189
187
|
|
190
188
|
def _check_sum(weights: NDArray[float64]) -> float64:
|
191
189
|
return cast(float64, npsum(weights) - 1)
|
@@ -196,7 +194,7 @@ def efficient_frontier( # noqa: C901
|
|
196
194
|
per_in_yr: float,
|
197
195
|
) -> NDArray[float64]:
|
198
196
|
ret = npsum(lg_ret.mean() * weights) * per_in_yr
|
199
|
-
volatility = sqrt(
|
197
|
+
volatility = sqrt(weights.T @ (lg_ret.cov() * per_in_yr @ weights))
|
200
198
|
sr = ret / volatility
|
201
199
|
return cast(NDArray[float64], array([ret, volatility, sr]))
|
202
200
|
|
@@ -509,6 +507,10 @@ def sharpeplot( # noqa: C901
|
|
509
507
|
fig, logo = load_plotly_dict()
|
510
508
|
figure = Figure(fig)
|
511
509
|
|
510
|
+
if sim_frame is None and line_frame is None and point_frame is None:
|
511
|
+
msg = "One of sim_frame, line_frame or point_frame must be provided."
|
512
|
+
raise ValueError(msg)
|
513
|
+
|
512
514
|
if sim_frame is not None:
|
513
515
|
returns.extend(list(sim_frame.loc[:, "ret"]))
|
514
516
|
risk.extend(list(sim_frame.loc[:, "stdev"]))
|
@@ -543,11 +545,11 @@ def sharpeplot( # noqa: C901
|
|
543
545
|
name="Efficient frontier",
|
544
546
|
)
|
545
547
|
|
546
|
-
colorway = fig["layout"].get("colorway")[ # type: ignore[union-attr]
|
547
|
-
: len(cast(DataFrame, point_frame).columns)
|
548
|
-
]
|
549
|
-
|
550
548
|
if point_frame is not None:
|
549
|
+
colorway = cast(
|
550
|
+
dict[str, str | int | float | bool | list[str]],
|
551
|
+
fig["layout"],
|
552
|
+
).get("colorway")[: len(point_frame.columns)]
|
551
553
|
for col, clr in zip(point_frame.columns, colorway):
|
552
554
|
returns.extend([point_frame.loc["ret", col]])
|
553
555
|
risk.extend([point_frame.loc["stdev", col]])
|
@@ -25,11 +25,11 @@ from pandas import (
|
|
25
25
|
Series,
|
26
26
|
date_range,
|
27
27
|
)
|
28
|
-
from pydantic import model_validator
|
28
|
+
from pydantic import field_validator, model_validator
|
29
29
|
from typing_extensions import Self
|
30
30
|
|
31
31
|
from ._common_model import _CommonModel
|
32
|
-
from .datefixer import
|
32
|
+
from .datefixer import _do_resample_to_business_period_ends, date_fix
|
33
33
|
from .types import (
|
34
34
|
Countries,
|
35
35
|
CountriesType,
|
@@ -105,8 +105,22 @@ class OpenTimeSeries(_CommonModel):
|
|
105
105
|
isin: str | None = None
|
106
106
|
label: str | None = None
|
107
107
|
|
108
|
+
@field_validator("domestic", mode="before")
|
109
|
+
@classmethod
|
110
|
+
def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
|
111
|
+
"""Pydantic validator to ensure domestic field is validated."""
|
112
|
+
_ = Currency(ccy=value)
|
113
|
+
return value
|
114
|
+
|
115
|
+
@field_validator("countries", mode="before")
|
116
|
+
@classmethod
|
117
|
+
def _validate_countries(cls, value: CountriesType) -> CountriesType:
|
118
|
+
"""Pydantic validator to ensure countries field is validated."""
|
119
|
+
_ = Countries(countryinput=value)
|
120
|
+
return value
|
121
|
+
|
108
122
|
@model_validator(mode="after") # type: ignore[misc,unused-ignore]
|
109
|
-
def
|
123
|
+
def _dates_and_values_validate(self: Self) -> Self:
|
110
124
|
"""Pydantic validator to ensure dates and values are validated."""
|
111
125
|
values_list_length = len(self.values)
|
112
126
|
dates_list_length = len(self.dates)
|
@@ -126,28 +140,6 @@ class OpenTimeSeries(_CommonModel):
|
|
126
140
|
raise ValueError(msg)
|
127
141
|
return self
|
128
142
|
|
129
|
-
@classmethod
|
130
|
-
def setup_class(
|
131
|
-
cls: type[OpenTimeSeries],
|
132
|
-
domestic_ccy: CurrencyStringType = "SEK",
|
133
|
-
countries: CountriesType = "SE",
|
134
|
-
) -> None:
|
135
|
-
"""Set the domestic currency and calendar of the user.
|
136
|
-
|
137
|
-
Parameters
|
138
|
-
----------
|
139
|
-
domestic_ccy : CurrencyStringType, default: "SEK"
|
140
|
-
Currency code according to ISO 4217
|
141
|
-
countries: CountriesType, default: "SE"
|
142
|
-
(List of) country code(s) according to ISO 3166-1 alpha-2
|
143
|
-
|
144
|
-
"""
|
145
|
-
_ = Currency(ccy=domestic_ccy)
|
146
|
-
_ = Countries(countryinput=countries)
|
147
|
-
|
148
|
-
cls.domestic = domestic_ccy
|
149
|
-
cls.countries = countries
|
150
|
-
|
151
143
|
@classmethod
|
152
144
|
def from_arrays(
|
153
145
|
cls: type[OpenTimeSeries],
|
@@ -241,14 +233,17 @@ class OpenTimeSeries(_CommonModel):
|
|
241
233
|
An OpenTimeSeries object
|
242
234
|
|
243
235
|
"""
|
244
|
-
|
245
|
-
|
236
|
+
msg = "Argument dframe must be pandas Series or DataFrame."
|
237
|
+
if isinstance(dframe, Series): # type: ignore[unreachable,unused-ignore]
|
238
|
+
if isinstance( # type: ignore[unreachable,unused-ignore]
|
239
|
+
dframe.name, tuple,
|
240
|
+
):
|
246
241
|
label, _ = dframe.name
|
247
242
|
else:
|
248
243
|
label = dframe.name
|
249
244
|
values = dframe.to_numpy().tolist()
|
250
|
-
|
251
|
-
values =
|
245
|
+
elif isinstance(dframe, DataFrame):
|
246
|
+
values = dframe.iloc[:, column_nmbr].to_list()
|
252
247
|
if isinstance(dframe.columns, MultiIndex):
|
253
248
|
if _check_if_none(
|
254
249
|
dframe.columns.get_level_values(0).to_numpy()[column_nmbr],
|
@@ -270,6 +265,9 @@ class OpenTimeSeries(_CommonModel):
|
|
270
265
|
]
|
271
266
|
else:
|
272
267
|
label = cast(MultiIndex, dframe.columns).to_numpy()[column_nmbr]
|
268
|
+
else:
|
269
|
+
raise TypeError(msg)
|
270
|
+
|
273
271
|
dates = [date_fix(d).strftime("%Y-%m-%d") for d in dframe.index]
|
274
272
|
|
275
273
|
return cls(
|
@@ -341,9 +339,7 @@ class OpenTimeSeries(_CommonModel):
|
|
341
339
|
)
|
342
340
|
elif not isinstance(d_range, DatetimeIndex) and not all([days, end_dt]):
|
343
341
|
msg = "If d_range is not provided both days and end_dt must be."
|
344
|
-
raise ValueError(
|
345
|
-
msg,
|
346
|
-
)
|
342
|
+
raise ValueError(msg)
|
347
343
|
|
348
344
|
deltas = array(
|
349
345
|
[
|
@@ -584,12 +580,8 @@ class OpenTimeSeries(_CommonModel):
|
|
584
580
|
An OpenTimeSeries object
|
585
581
|
|
586
582
|
"""
|
587
|
-
|
588
|
-
tail = self.tsdf.iloc[-1].copy()
|
589
|
-
dates = do_resample_to_business_period_ends(
|
583
|
+
dates = _do_resample_to_business_period_ends(
|
590
584
|
data=self.tsdf,
|
591
|
-
head=head,
|
592
|
-
tail=tail,
|
593
585
|
freq=freq,
|
594
586
|
countries=self.countries,
|
595
587
|
)
|
@@ -153,8 +153,8 @@ class ReturnSimulation(BaseModel):
|
|
153
153
|
mean_annual_return: float,
|
154
154
|
mean_annual_vol: PositiveFloat,
|
155
155
|
trading_days: PositiveInt,
|
156
|
-
seed: int,
|
157
156
|
trading_days_in_year: DaysInYearType = 252,
|
157
|
+
seed: int | None = None,
|
158
158
|
randomizer: Generator | None = None,
|
159
159
|
) -> ReturnSimulation:
|
160
160
|
"""Create a Normal distribution simulation.
|
@@ -169,11 +169,10 @@ class ReturnSimulation(BaseModel):
|
|
169
169
|
Mean return
|
170
170
|
mean_annual_vol: PositiveFloat
|
171
171
|
Mean standard deviation
|
172
|
-
|
173
|
-
Seed for random process initiation
|
174
|
-
trading_days_in_year: DaysInYearType,
|
175
|
-
default: 252
|
172
|
+
trading_days_in_year: DaysInYearType, default: 252
|
176
173
|
Number of trading days used to annualize
|
174
|
+
seed: int, optional
|
175
|
+
Seed for random process initiation
|
177
176
|
randomizer: numpy.random.Generator, optional
|
178
177
|
Random process generator
|
179
178
|
|
@@ -209,8 +208,8 @@ class ReturnSimulation(BaseModel):
|
|
209
208
|
mean_annual_return: float,
|
210
209
|
mean_annual_vol: PositiveFloat,
|
211
210
|
trading_days: PositiveInt,
|
212
|
-
seed: int,
|
213
211
|
trading_days_in_year: DaysInYearType = 252,
|
212
|
+
seed: int | None = None,
|
214
213
|
randomizer: Generator | None = None,
|
215
214
|
) -> ReturnSimulation:
|
216
215
|
"""Create a Lognormal distribution simulation.
|
@@ -225,11 +224,10 @@ class ReturnSimulation(BaseModel):
|
|
225
224
|
Mean return
|
226
225
|
mean_annual_vol: PositiveFloat
|
227
226
|
Mean standard deviation
|
228
|
-
|
229
|
-
Seed for random process initiation
|
230
|
-
trading_days_in_year: DaysInYearType,
|
231
|
-
default: 252
|
227
|
+
trading_days_in_year: DaysInYearType, default: 252
|
232
228
|
Number of trading days used to annualize
|
229
|
+
seed: int, optional
|
230
|
+
Seed for random process initiation
|
233
231
|
randomizer: numpy.random.Generator, optional
|
234
232
|
Random process generator
|
235
233
|
|
@@ -268,8 +266,8 @@ class ReturnSimulation(BaseModel):
|
|
268
266
|
mean_annual_return: float,
|
269
267
|
mean_annual_vol: PositiveFloat,
|
270
268
|
trading_days: PositiveInt,
|
271
|
-
seed: int,
|
272
269
|
trading_days_in_year: DaysInYearType = 252,
|
270
|
+
seed: int | None = None,
|
273
271
|
randomizer: Generator | None = None,
|
274
272
|
) -> ReturnSimulation:
|
275
273
|
"""Create a Geometric Brownian Motion simulation.
|
@@ -284,10 +282,10 @@ class ReturnSimulation(BaseModel):
|
|
284
282
|
Mean return
|
285
283
|
mean_annual_vol: PositiveFloat
|
286
284
|
Mean standard deviation
|
287
|
-
seed: int
|
288
|
-
Seed for random process initiation
|
289
285
|
trading_days_in_year: DaysInYearType, default: 252
|
290
286
|
Number of trading days used to annualize
|
287
|
+
seed: int, optional
|
288
|
+
Seed for random process initiation
|
291
289
|
randomizer: numpy.random.Generator, optional
|
292
290
|
Random process generator
|
293
291
|
|
@@ -330,11 +328,11 @@ class ReturnSimulation(BaseModel):
|
|
330
328
|
trading_days: PositiveInt,
|
331
329
|
mean_annual_return: float,
|
332
330
|
mean_annual_vol: PositiveFloat,
|
333
|
-
seed: int,
|
334
331
|
jumps_lamda: NonNegativeFloat,
|
335
332
|
jumps_sigma: NonNegativeFloat = 0.0,
|
336
333
|
jumps_mu: float = 0.0,
|
337
334
|
trading_days_in_year: DaysInYearType = 252,
|
335
|
+
seed: int | None = None,
|
338
336
|
randomizer: Generator | None = None,
|
339
337
|
) -> ReturnSimulation:
|
340
338
|
"""Create a Merton Jump-Diffusion model simulation.
|
@@ -349,8 +347,6 @@ class ReturnSimulation(BaseModel):
|
|
349
347
|
Mean return
|
350
348
|
mean_annual_vol: PositiveFloat
|
351
349
|
Mean standard deviation
|
352
|
-
seed: int
|
353
|
-
Seed for random process initiation
|
354
350
|
jumps_lamda: NonNegativeFloat
|
355
351
|
This is the probability of a jump happening at each point in time
|
356
352
|
jumps_sigma: NonNegativeFloat, default: 0.0
|
@@ -359,6 +355,8 @@ class ReturnSimulation(BaseModel):
|
|
359
355
|
This is the average jump size
|
360
356
|
trading_days_in_year: DaysInYearType, default: 252
|
361
357
|
Number of trading days used to annualize
|
358
|
+
seed: int, optional
|
359
|
+
Seed for random process initiation
|
362
360
|
randomizer: numpy.random.Generator, optional
|
363
361
|
Random process generator
|
364
362
|
|
@@ -272,9 +272,7 @@ class OpenTimeSeriesPropertiesList(list[str]):
|
|
272
272
|
msg = (
|
273
273
|
f"Invalid string: {item}. Allowed strings: {self.allowed_strings}"
|
274
274
|
)
|
275
|
-
raise ValueError(
|
276
|
-
msg,
|
277
|
-
)
|
275
|
+
raise ValueError(msg)
|
278
276
|
if item in seen:
|
279
277
|
msg = f"Duplicate string: {item}"
|
280
278
|
raise ValueError(msg)
|
@@ -323,9 +321,7 @@ class OpenFramePropertiesList(list[str]):
|
|
323
321
|
msg = (
|
324
322
|
f"Invalid string: {item}. Allowed strings: {self.allowed_strings}"
|
325
323
|
)
|
326
|
-
raise ValueError(
|
327
|
-
msg,
|
328
|
-
)
|
324
|
+
raise ValueError(msg)
|
329
325
|
if item in seen:
|
330
326
|
msg = f"Duplicate string: {item}"
|
331
327
|
raise ValueError(msg)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "openseries"
|
3
|
-
version = "1.7.
|
3
|
+
version = "1.7.2"
|
4
4
|
description = "Tools for analyzing financial timeseries."
|
5
5
|
authors = ["Martin Karrin <martin.karrin@captor.se>"]
|
6
6
|
repository = "https://github.com/CaptorAB/openseries"
|
@@ -42,18 +42,18 @@ pyarrow = ">=14.0.2,<18.0.0"
|
|
42
42
|
pydantic = ">=2.5.2,<3.0.0"
|
43
43
|
python-dateutil = ">=2.8.2,<3.0.0"
|
44
44
|
requests = ">=2.20.0,<3.0.0"
|
45
|
-
scipy = ">=1.11.4,<
|
45
|
+
scipy = ">=1.11.4,<1.14.1"
|
46
46
|
statsmodels = ">=0.14.0,<1.0.0"
|
47
47
|
|
48
48
|
[tool.poetry.group.dev.dependencies]
|
49
49
|
black = ">=24.4.2,<25.0.0"
|
50
50
|
coverage = ">=7.6.0,<8.0.0"
|
51
|
-
|
52
|
-
mypy = "^1.11.
|
51
|
+
genbadge = {version = ">=1.1.1,<2.0.0", extras = ["coverage"]}
|
52
|
+
mypy = "^1.11.1"
|
53
53
|
pandas-stubs = ">=2.1.2,<3.0.0"
|
54
54
|
pre-commit = ">=3.7.1,<4.0.0"
|
55
55
|
pytest = ">=8.2.2,<9.0.0"
|
56
|
-
ruff = "^0.
|
56
|
+
ruff = "^0.6.2"
|
57
57
|
types-openpyxl = ">=3.1.2,<4.0.0"
|
58
58
|
types-python-dateutil = ">=2.8.2,<3.0.0"
|
59
59
|
types-requests = ">=2.20.0,<3.0.0"
|
@@ -63,18 +63,23 @@ requires = ["poetry-core>=1.8.3"]
|
|
63
63
|
build-backend = "poetry.core.masonry.api"
|
64
64
|
|
65
65
|
[tool.coverage.run]
|
66
|
+
branch = true
|
66
67
|
omit = ["venv/*"]
|
67
68
|
include = ["openseries/*"]
|
68
69
|
|
69
70
|
[tool.coverage.report]
|
70
71
|
skip_empty = true
|
72
|
+
show_missing = true
|
71
73
|
fail_under = 99
|
72
74
|
|
75
|
+
[tool.coverage.xml]
|
76
|
+
output = "coverage.xml"
|
77
|
+
|
73
78
|
[tool.mypy]
|
74
79
|
exclude = ["venv/*"]
|
75
80
|
strict = true
|
76
81
|
pretty = true
|
77
|
-
warn_unreachable =
|
82
|
+
warn_unreachable = true
|
78
83
|
warn_redundant_casts = true
|
79
84
|
warn_unused_ignores = true
|
80
85
|
disallow_any_generics = true
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|