openseries 1.9.4__py3-none-any.whl → 1.9.6__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/_common_model.py +373 -451
- openseries/datefixer.py +8 -6
- openseries/frame.py +110 -93
- openseries/owntypes.py +48 -47
- openseries/plotly_layouts.json +1 -1
- openseries/portfoliotools.py +10 -12
- openseries/report.py +66 -61
- openseries/series.py +51 -37
- openseries/simulation.py +3 -3
- {openseries-1.9.4.dist-info → openseries-1.9.6.dist-info}/METADATA +4 -3
- openseries-1.9.6.dist-info/RECORD +17 -0
- {openseries-1.9.4.dist-info → openseries-1.9.6.dist-info}/WHEEL +1 -1
- openseries-1.9.4.dist-info/RECORD +0 -17
- {openseries-1.9.4.dist-info → openseries-1.9.6.dist-info/licenses}/LICENSE.md +0 -0
openseries/datefixer.py
CHANGED
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
12
12
|
import datetime as dt
|
13
13
|
from typing import TYPE_CHECKING, cast
|
14
14
|
|
15
|
-
import exchange_calendars as exchcal
|
15
|
+
import exchange_calendars as exchcal # type: ignore[import-untyped]
|
16
16
|
from dateutil.relativedelta import relativedelta
|
17
17
|
from holidays import (
|
18
18
|
country_holidays,
|
@@ -144,7 +144,7 @@ def holiday_calendar(
|
|
144
144
|
for country in countries:
|
145
145
|
staging = country_holidays(country=country, years=years)
|
146
146
|
countryholidays += list(staging)
|
147
|
-
hols = list(countryholidays)
|
147
|
+
hols = cast("list[dt.date]", list(countryholidays))
|
148
148
|
else:
|
149
149
|
msg = (
|
150
150
|
"Argument countries must be a string country code or "
|
@@ -154,7 +154,9 @@ def holiday_calendar(
|
|
154
154
|
|
155
155
|
if markets:
|
156
156
|
market_hols = market_holidays(
|
157
|
-
startyear=startyear,
|
157
|
+
startyear=startyear,
|
158
|
+
endyear=endyear,
|
159
|
+
markets=markets,
|
158
160
|
)
|
159
161
|
dt_mkt_hols = [date_fix(fixerdate=ddate) for ddate in market_hols]
|
160
162
|
hols.extend(dt_mkt_hols)
|
@@ -163,7 +165,7 @@ def holiday_calendar(
|
|
163
165
|
custom_list = (
|
164
166
|
[custom_holidays]
|
165
167
|
if isinstance(custom_holidays, str)
|
166
|
-
else list(custom_holidays)
|
168
|
+
else list(custom_holidays)
|
167
169
|
)
|
168
170
|
hols.extend([date_fix(fixerdate=ddate) for ddate in custom_list])
|
169
171
|
|
@@ -255,7 +257,7 @@ def date_offset_foll(
|
|
255
257
|
while not is_busday(dates=new_date, busdaycal=calendar):
|
256
258
|
new_date += day_delta
|
257
259
|
|
258
|
-
return new_date
|
260
|
+
return new_date
|
259
261
|
|
260
262
|
|
261
263
|
def get_previous_business_day_before_today(
|
@@ -505,7 +507,7 @@ def _do_resample_to_business_period_ends(
|
|
505
507
|
dates = DatetimeIndex(
|
506
508
|
[copydata.index[0]]
|
507
509
|
+ [
|
508
|
-
date_offset_foll(
|
510
|
+
date_offset_foll(
|
509
511
|
raw_date=dt.date(d.year, d.month, 1)
|
510
512
|
+ relativedelta(months=1)
|
511
513
|
- dt.timedelta(days=1),
|
openseries/frame.py
CHANGED
@@ -7,7 +7,6 @@ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
|
|
7
7
|
SPDX-License-Identifier: BSD-3-Clause
|
8
8
|
"""
|
9
9
|
|
10
|
-
# mypy: disable-error-code="assignment,no-any-return"
|
11
10
|
from __future__ import annotations
|
12
11
|
|
13
12
|
from copy import deepcopy
|
@@ -15,12 +14,11 @@ from functools import reduce
|
|
15
14
|
from logging import getLogger
|
16
15
|
from typing import TYPE_CHECKING, Any, cast
|
17
16
|
|
18
|
-
if TYPE_CHECKING: # pragma: no cover
|
19
|
-
import datetime as dt
|
20
|
-
|
21
17
|
from numpy import (
|
22
18
|
array,
|
19
|
+
concatenate,
|
23
20
|
cov,
|
21
|
+
diff,
|
24
22
|
divide,
|
25
23
|
isinf,
|
26
24
|
log,
|
@@ -37,8 +35,19 @@ from pandas import (
|
|
37
35
|
concat,
|
38
36
|
merge,
|
39
37
|
)
|
38
|
+
|
39
|
+
if TYPE_CHECKING: # pragma: no cover
|
40
|
+
import datetime as dt
|
41
|
+
|
42
|
+
from pandas import Series as _Series
|
43
|
+
from pandas import Timestamp
|
44
|
+
|
45
|
+
SeriesFloat = _Series[float]
|
46
|
+
else:
|
47
|
+
SeriesFloat = Series
|
48
|
+
|
40
49
|
from pydantic import field_validator
|
41
|
-
from sklearn.linear_model import LinearRegression
|
50
|
+
from sklearn.linear_model import LinearRegression # type: ignore[import-untyped]
|
42
51
|
|
43
52
|
from ._common_model import _CommonModel
|
44
53
|
from .datefixer import _do_resample_to_business_period_ends
|
@@ -69,7 +78,7 @@ __all__ = ["OpenFrame"]
|
|
69
78
|
|
70
79
|
|
71
80
|
# noinspection PyUnresolvedReferences,PyTypeChecker
|
72
|
-
class OpenFrame(_CommonModel):
|
81
|
+
class OpenFrame(_CommonModel[SeriesFloat]):
|
73
82
|
"""OpenFrame objects hold OpenTimeSeries in the list constituents.
|
74
83
|
|
75
84
|
The intended use is to allow comparisons across these timeseries.
|
@@ -93,9 +102,9 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
93
102
|
weights: list[float] | None = None
|
94
103
|
|
95
104
|
# noinspection PyMethodParameters
|
96
|
-
@field_validator("constituents")
|
97
|
-
def _check_labels_unique(
|
98
|
-
cls: OpenFrame, # noqa: N805
|
105
|
+
@field_validator("constituents")
|
106
|
+
def _check_labels_unique(
|
107
|
+
cls: type[OpenFrame], # noqa: N805
|
99
108
|
tseries: list[OpenTimeSeries],
|
100
109
|
) -> list[OpenTimeSeries]:
|
101
110
|
"""Pydantic validator ensuring that OpenFrame labels are unique."""
|
@@ -240,7 +249,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
240
249
|
|
241
250
|
"""
|
242
251
|
return Series(
|
243
|
-
data=[self.tsdf
|
252
|
+
data=[self.tsdf[col].count() for col in self.tsdf.columns],
|
244
253
|
index=self.tsdf.columns,
|
245
254
|
name="observations",
|
246
255
|
).astype(int)
|
@@ -556,12 +565,14 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
556
565
|
|
557
566
|
"""
|
558
567
|
earlier, later = self.calc_range(
|
559
|
-
months_offset=months_from_last,
|
568
|
+
months_offset=months_from_last,
|
569
|
+
from_dt=from_date,
|
570
|
+
to_dt=to_date,
|
560
571
|
)
|
561
572
|
if periods_in_a_year_fixed is None:
|
562
573
|
fraction = (later - earlier).days / 365.25
|
563
574
|
how_many = (
|
564
|
-
self.tsdf.loc[cast("
|
575
|
+
self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
|
565
576
|
.count()
|
566
577
|
.iloc[0]
|
567
578
|
)
|
@@ -579,33 +590,36 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
579
590
|
cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0],
|
580
591
|
]
|
581
592
|
|
582
|
-
data = self.tsdf.loc[
|
593
|
+
data = self.tsdf.loc[
|
594
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
595
|
+
].copy()
|
583
596
|
|
584
597
|
for rtn in cols:
|
585
|
-
|
598
|
+
arr = concatenate([array([nan]), diff(log(data[(rtn, ValueType.PRICE)]))])
|
599
|
+
data[rtn, ValueType.RTRN] = arr
|
586
600
|
|
587
601
|
raw_one = [
|
588
|
-
data
|
602
|
+
data[(cols[0], ValueType.RTRN)]
|
589
603
|
.iloc[1:day_chunk]
|
590
604
|
.std(ddof=dlta_degr_freedms)
|
591
605
|
* sqrt(time_factor),
|
592
606
|
]
|
593
607
|
raw_two = [
|
594
|
-
data
|
608
|
+
data[(cols[1], ValueType.RTRN)]
|
595
609
|
.iloc[1:day_chunk]
|
596
610
|
.std(ddof=dlta_degr_freedms)
|
597
611
|
* sqrt(time_factor),
|
598
612
|
]
|
599
613
|
raw_cov = [
|
600
614
|
cov(
|
601
|
-
m=data
|
602
|
-
y=data
|
615
|
+
m=data[(cols[0], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
|
616
|
+
y=data[(cols[1], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
|
603
617
|
ddof=dlta_degr_freedms,
|
604
618
|
)[0][1],
|
605
619
|
]
|
606
620
|
|
607
|
-
r1 = data
|
608
|
-
r2 = data
|
621
|
+
r1 = data[(cols[0], ValueType.RTRN)]
|
622
|
+
r2 = data[(cols[1], ValueType.RTRN)]
|
609
623
|
|
610
624
|
alpha = 1.0 - lmbda
|
611
625
|
|
@@ -837,30 +851,30 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
837
851
|
|
838
852
|
"""
|
839
853
|
earlier, later = self.calc_range(
|
840
|
-
months_offset=months_from_last,
|
854
|
+
months_offset=months_from_last,
|
855
|
+
from_dt=from_date,
|
856
|
+
to_dt=to_date,
|
841
857
|
)
|
842
|
-
fraction = (later - earlier).days / 365.25
|
858
|
+
fraction: float = (later - earlier).days / 365.25
|
843
859
|
|
844
860
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
845
861
|
if isinstance(base_column, tuple):
|
846
|
-
shortdf = self.tsdf.loc[
|
847
|
-
|
848
|
-
|
849
|
-
]
|
862
|
+
shortdf = self.tsdf.loc[
|
863
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
864
|
+
][base_column]
|
850
865
|
short_item = base_column
|
851
866
|
short_label = cast(
|
852
867
|
"tuple[str, ValueType]",
|
853
|
-
self.tsdf
|
868
|
+
self.tsdf[base_column].name,
|
854
869
|
)[0]
|
855
870
|
elif isinstance(base_column, int):
|
856
|
-
shortdf = self.tsdf.loc[
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
:,
|
862
|
-
|
863
|
-
].name
|
871
|
+
shortdf = self.tsdf.loc[
|
872
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
873
|
+
].iloc[:, base_column]
|
874
|
+
short_item = cast(
|
875
|
+
"tuple[str, ValueType]",
|
876
|
+
self.tsdf.iloc[:, base_column].name,
|
877
|
+
)
|
864
878
|
short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
|
865
879
|
0
|
866
880
|
]
|
@@ -870,17 +884,16 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
870
884
|
if periods_in_a_year_fixed:
|
871
885
|
time_factor = float(periods_in_a_year_fixed)
|
872
886
|
else:
|
873
|
-
time_factor =
|
887
|
+
time_factor = shortdf.count() / fraction
|
874
888
|
|
875
889
|
terrors = []
|
876
890
|
for item in self.tsdf:
|
877
891
|
if item == short_item:
|
878
892
|
terrors.append(0.0)
|
879
893
|
else:
|
880
|
-
longdf = self.tsdf.loc[
|
881
|
-
|
882
|
-
|
883
|
-
]
|
894
|
+
longdf = self.tsdf.loc[
|
895
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
896
|
+
][item]
|
884
897
|
relative = longdf.ffill().pct_change() - shortdf.ffill().pct_change()
|
885
898
|
vol = float(relative.std() * sqrt(time_factor))
|
886
899
|
terrors.append(vol)
|
@@ -929,30 +942,33 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
929
942
|
|
930
943
|
"""
|
931
944
|
earlier, later = self.calc_range(
|
932
|
-
months_offset=months_from_last,
|
945
|
+
months_offset=months_from_last,
|
946
|
+
from_dt=from_date,
|
947
|
+
to_dt=to_date,
|
933
948
|
)
|
934
|
-
fraction = (later - earlier).days / 365.25
|
949
|
+
fraction: float = (later - earlier).days / 365.25
|
935
950
|
|
936
951
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
937
952
|
if isinstance(base_column, tuple):
|
938
|
-
shortdf = self.tsdf.loc[
|
939
|
-
|
940
|
-
|
941
|
-
]
|
953
|
+
shortdf = self.tsdf.loc[
|
954
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
955
|
+
][base_column]
|
942
956
|
short_item = base_column
|
943
957
|
short_label = cast(
|
944
958
|
"tuple[str, str]",
|
945
|
-
self.tsdf
|
959
|
+
self.tsdf[base_column].name,
|
946
960
|
)[0]
|
947
961
|
elif isinstance(base_column, int):
|
948
|
-
shortdf = self.tsdf.loc[
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
962
|
+
shortdf = self.tsdf.loc[
|
963
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
964
|
+
].iloc[:, base_column]
|
965
|
+
short_item = cast(
|
966
|
+
"tuple[str, ValueType]",
|
967
|
+
self.tsdf.iloc[
|
968
|
+
:,
|
969
|
+
base_column,
|
970
|
+
].name,
|
971
|
+
)
|
956
972
|
short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
|
957
973
|
0
|
958
974
|
]
|
@@ -962,17 +978,16 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
962
978
|
if periods_in_a_year_fixed:
|
963
979
|
time_factor = float(periods_in_a_year_fixed)
|
964
980
|
else:
|
965
|
-
time_factor =
|
981
|
+
time_factor = shortdf.count() / fraction
|
966
982
|
|
967
983
|
ratios = []
|
968
984
|
for item in self.tsdf:
|
969
985
|
if item == short_item:
|
970
986
|
ratios.append(0.0)
|
971
987
|
else:
|
972
|
-
longdf = self.tsdf.loc[
|
973
|
-
|
974
|
-
|
975
|
-
]
|
988
|
+
longdf = self.tsdf.loc[
|
989
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
990
|
+
][item]
|
976
991
|
relative = longdf.ffill().pct_change() - shortdf.ffill().pct_change()
|
977
992
|
ret = float(relative.mean() * time_factor)
|
978
993
|
vol = float(relative.std() * sqrt(time_factor))
|
@@ -1030,30 +1045,33 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1030
1045
|
"""
|
1031
1046
|
loss_limit: float = 0.0
|
1032
1047
|
earlier, later = self.calc_range(
|
1033
|
-
months_offset=months_from_last,
|
1048
|
+
months_offset=months_from_last,
|
1049
|
+
from_dt=from_date,
|
1050
|
+
to_dt=to_date,
|
1034
1051
|
)
|
1035
|
-
fraction = (later - earlier).days / 365.25
|
1052
|
+
fraction: float = (later - earlier).days / 365.25
|
1036
1053
|
|
1037
1054
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
1038
1055
|
if isinstance(base_column, tuple):
|
1039
|
-
shortdf = self.tsdf.loc[
|
1040
|
-
|
1041
|
-
|
1042
|
-
]
|
1056
|
+
shortdf = self.tsdf.loc[
|
1057
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
1058
|
+
][base_column]
|
1043
1059
|
short_item = base_column
|
1044
1060
|
short_label = cast(
|
1045
1061
|
"tuple[str, str]",
|
1046
|
-
self.tsdf
|
1062
|
+
self.tsdf[base_column].name,
|
1047
1063
|
)[0]
|
1048
1064
|
elif isinstance(base_column, int):
|
1049
|
-
shortdf = self.tsdf.loc[
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1065
|
+
shortdf = self.tsdf.loc[
|
1066
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
1067
|
+
].iloc[:, base_column]
|
1068
|
+
short_item = cast(
|
1069
|
+
"tuple[str, ValueType]",
|
1070
|
+
self.tsdf.iloc[
|
1071
|
+
:,
|
1072
|
+
base_column,
|
1073
|
+
].name,
|
1074
|
+
)
|
1057
1075
|
short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
|
1058
1076
|
0
|
1059
1077
|
]
|
@@ -1063,17 +1081,16 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1063
1081
|
if periods_in_a_year_fixed:
|
1064
1082
|
time_factor = float(periods_in_a_year_fixed)
|
1065
1083
|
else:
|
1066
|
-
time_factor =
|
1084
|
+
time_factor = shortdf.count() / fraction
|
1067
1085
|
|
1068
1086
|
ratios = []
|
1069
1087
|
for item in self.tsdf:
|
1070
1088
|
if item == short_item:
|
1071
1089
|
ratios.append(0.0)
|
1072
1090
|
else:
|
1073
|
-
longdf = self.tsdf.loc[
|
1074
|
-
|
1075
|
-
|
1076
|
-
]
|
1091
|
+
longdf = self.tsdf.loc[
|
1092
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
1093
|
+
][item]
|
1077
1094
|
msg = "ratio must be one of 'up', 'down' or 'both'."
|
1078
1095
|
if ratio == "up":
|
1079
1096
|
uparray = (
|
@@ -1216,7 +1233,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1216
1233
|
if all(vtypes):
|
1217
1234
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1218
1235
|
if isinstance(asset, tuple):
|
1219
|
-
y_value = self.tsdf
|
1236
|
+
y_value = self.tsdf[asset]
|
1220
1237
|
elif isinstance(asset, int):
|
1221
1238
|
y_value = self.tsdf.iloc[:, asset]
|
1222
1239
|
else:
|
@@ -1224,7 +1241,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1224
1241
|
|
1225
1242
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1226
1243
|
if isinstance(market, tuple):
|
1227
|
-
x_value = self.tsdf
|
1244
|
+
x_value = self.tsdf[market]
|
1228
1245
|
elif isinstance(market, int):
|
1229
1246
|
x_value = self.tsdf.iloc[:, market]
|
1230
1247
|
else:
|
@@ -1232,7 +1249,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1232
1249
|
elif not any(vtypes):
|
1233
1250
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1234
1251
|
if isinstance(asset, tuple):
|
1235
|
-
y_value = self.tsdf
|
1252
|
+
y_value = self.tsdf[asset].ffill().pct_change().iloc[1:]
|
1236
1253
|
elif isinstance(asset, int):
|
1237
1254
|
y_value = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
|
1238
1255
|
else:
|
@@ -1240,7 +1257,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1240
1257
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1241
1258
|
|
1242
1259
|
if isinstance(market, tuple):
|
1243
|
-
x_value = self.tsdf
|
1260
|
+
x_value = self.tsdf[market].ffill().pct_change().iloc[1:]
|
1244
1261
|
elif isinstance(market, int):
|
1245
1262
|
x_value = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
|
1246
1263
|
else:
|
@@ -1283,10 +1300,10 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1283
1300
|
"""
|
1284
1301
|
msg = "y_column should be a tuple[str, ValueType] or an integer."
|
1285
1302
|
if isinstance(y_column, tuple):
|
1286
|
-
y_value = self.tsdf
|
1303
|
+
y_value = self.tsdf[y_column].to_numpy()
|
1287
1304
|
y_label = cast(
|
1288
1305
|
"tuple[str, str]",
|
1289
|
-
self.tsdf
|
1306
|
+
self.tsdf[y_column].name,
|
1290
1307
|
)[0]
|
1291
1308
|
elif isinstance(y_column, int):
|
1292
1309
|
y_value = self.tsdf.iloc[:, y_column].to_numpy()
|
@@ -1296,10 +1313,10 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1296
1313
|
|
1297
1314
|
msg = "x_column should be a tuple[str, ValueType] or an integer."
|
1298
1315
|
if isinstance(x_column, tuple):
|
1299
|
-
x_value = self.tsdf
|
1316
|
+
x_value = self.tsdf[x_column].to_numpy().reshape(-1, 1)
|
1300
1317
|
x_label = cast(
|
1301
1318
|
"tuple[str, str]",
|
1302
|
-
self.tsdf
|
1319
|
+
self.tsdf[x_column].name,
|
1303
1320
|
)[0]
|
1304
1321
|
elif isinstance(x_column, int):
|
1305
1322
|
x_value = self.tsdf.iloc[:, x_column].to_numpy().reshape(-1, 1)
|
@@ -1354,7 +1371,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1354
1371
|
if not any(vtypes):
|
1355
1372
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1356
1373
|
if isinstance(asset, tuple):
|
1357
|
-
asset_rtn = self.tsdf
|
1374
|
+
asset_rtn = self.tsdf[asset].ffill().pct_change().iloc[1:]
|
1358
1375
|
asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
|
1359
1376
|
elif isinstance(asset, int):
|
1360
1377
|
asset_rtn = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
|
@@ -1364,7 +1381,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1364
1381
|
|
1365
1382
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1366
1383
|
if isinstance(market, tuple):
|
1367
|
-
market_rtn = self.tsdf
|
1384
|
+
market_rtn = self.tsdf[market].ffill().pct_change().iloc[1:]
|
1368
1385
|
market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
|
1369
1386
|
elif isinstance(market, int):
|
1370
1387
|
market_rtn = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
|
@@ -1374,7 +1391,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1374
1391
|
elif all(vtypes):
|
1375
1392
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1376
1393
|
if isinstance(asset, tuple):
|
1377
|
-
asset_rtn = self.tsdf
|
1394
|
+
asset_rtn = self.tsdf[asset]
|
1378
1395
|
asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
|
1379
1396
|
elif isinstance(asset, int):
|
1380
1397
|
asset_rtn = self.tsdf.iloc[:, asset]
|
@@ -1384,7 +1401,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1384
1401
|
|
1385
1402
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1386
1403
|
if isinstance(market, tuple):
|
1387
|
-
market_rtn = self.tsdf
|
1404
|
+
market_rtn = self.tsdf[market]
|
1388
1405
|
market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
|
1389
1406
|
elif isinstance(market, int):
|
1390
1407
|
market_rtn = self.tsdf.iloc[:, market]
|
@@ -1399,7 +1416,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
|
|
1399
1416
|
beta = covariance[0, 1] / covariance[1, 1]
|
1400
1417
|
|
1401
1418
|
return float(
|
1402
|
-
asset_rtn_mean - riskfree_rate - beta * (market_rtn_mean - riskfree_rate)
|
1419
|
+
asset_rtn_mean - riskfree_rate - beta * (market_rtn_mean - riskfree_rate),
|
1403
1420
|
)
|
1404
1421
|
|
1405
1422
|
def make_portfolio(
|
openseries/owntypes.py
CHANGED
@@ -12,14 +12,22 @@ from __future__ import annotations
|
|
12
12
|
import datetime as dt
|
13
13
|
from enum import Enum
|
14
14
|
from pprint import pformat
|
15
|
-
from typing import Annotated, ClassVar, Literal,
|
15
|
+
from typing import TYPE_CHECKING, Annotated, ClassVar, Literal, TypeAlias, TypeVar
|
16
16
|
|
17
|
+
from annotated_types import MinLen
|
17
18
|
from numpy import datetime64
|
18
|
-
from pandas import Timestamp
|
19
|
-
from pydantic import BaseModel, Field, StringConstraints
|
19
|
+
from pandas import Series, Timestamp
|
20
|
+
from pydantic import BaseModel, Field, StringConstraints
|
21
|
+
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from pandas import Series as _Series
|
24
|
+
|
25
|
+
SeriesFloat = _Series[float]
|
26
|
+
else:
|
27
|
+
SeriesFloat = Series
|
20
28
|
|
21
29
|
try:
|
22
|
-
from typing import Self
|
30
|
+
from typing import Self
|
23
31
|
except ImportError: # pragma: no cover
|
24
32
|
from typing_extensions import Self
|
25
33
|
|
@@ -27,6 +35,9 @@ except ImportError: # pragma: no cover
|
|
27
35
|
__all__ = ["Self", "ValueType"]
|
28
36
|
|
29
37
|
|
38
|
+
Combo_co = TypeVar("Combo_co", float, SeriesFloat, covariant=True)
|
39
|
+
|
40
|
+
|
30
41
|
CountryStringType = Annotated[
|
31
42
|
str,
|
32
43
|
StringConstraints(
|
@@ -38,14 +49,11 @@ CountryStringType = Annotated[
|
|
38
49
|
strict=True,
|
39
50
|
),
|
40
51
|
]
|
41
|
-
|
42
|
-
|
43
|
-
min_length=1,
|
44
|
-
)
|
45
|
-
CountriesType = Union[CountryListType, CountryStringType] # type: ignore[valid-type] # noqa: UP007
|
52
|
+
CountrySetType: TypeAlias = Annotated[set[CountryStringType], MinLen(1)]
|
53
|
+
CountriesType: TypeAlias = CountrySetType | CountryStringType
|
46
54
|
|
47
55
|
|
48
|
-
class Countries(BaseModel):
|
56
|
+
class Countries(BaseModel):
|
49
57
|
"""Declare Countries."""
|
50
58
|
|
51
59
|
countryinput: CountriesType
|
@@ -64,69 +72,62 @@ CurrencyStringType = Annotated[
|
|
64
72
|
]
|
65
73
|
|
66
74
|
|
67
|
-
class Currency(BaseModel):
|
75
|
+
class Currency(BaseModel):
|
68
76
|
"""Declare Currency."""
|
69
77
|
|
70
78
|
ccy: CurrencyStringType
|
71
79
|
|
72
80
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
strict=True,
|
82
|
-
min_length=10,
|
83
|
-
max_length=10,
|
84
|
-
),
|
85
|
-
],
|
86
|
-
min_length=1,
|
81
|
+
DateStringType = Annotated[
|
82
|
+
str,
|
83
|
+
StringConstraints(
|
84
|
+
pattern=r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$",
|
85
|
+
strip_whitespace=True,
|
86
|
+
strict=True,
|
87
|
+
min_length=10,
|
88
|
+
max_length=10,
|
87
89
|
),
|
88
90
|
]
|
91
|
+
DateListType: TypeAlias = Annotated[list[DateStringType], MinLen(1)]
|
89
92
|
|
90
|
-
ValueListType = Annotated[list[float],
|
93
|
+
ValueListType: TypeAlias = Annotated[list[float], MinLen(1)]
|
91
94
|
|
92
95
|
DaysInYearType = Annotated[int, Field(strict=True, ge=1, le=366)]
|
93
96
|
|
94
97
|
DateType = str | dt.date | dt.datetime | datetime64 | Timestamp
|
95
98
|
|
96
|
-
|
97
|
-
str,
|
99
|
+
PlotlyConfigType = (
|
98
100
|
str
|
99
101
|
| int
|
100
102
|
| float
|
101
103
|
| bool
|
102
104
|
| list[str]
|
103
|
-
| dict[str, str | int | float | bool | list[str]]
|
104
|
-
|
105
|
+
| dict[str, str | int | float | bool | list[str]]
|
106
|
+
)
|
107
|
+
|
108
|
+
PlotlyLayoutType = dict[str, PlotlyConfigType]
|
105
109
|
|
106
110
|
CaptorLogoType = dict[str, str | float]
|
107
111
|
|
108
112
|
LiteralJsonOutput = Literal["values", "tsdf"]
|
109
113
|
LiteralTrunc = Literal["before", "after", "both"]
|
110
|
-
LiteralLinePlotMode =
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
]
|
114
|
+
LiteralLinePlotMode = (
|
115
|
+
Literal[
|
116
|
+
"lines",
|
117
|
+
"markers",
|
118
|
+
"lines+markers",
|
119
|
+
"lines+text",
|
120
|
+
"markers+text",
|
121
|
+
"lines+markers+text",
|
122
|
+
]
|
123
|
+
| None
|
124
|
+
)
|
119
125
|
LiteralHowMerge = Literal["outer", "inner"]
|
120
126
|
LiteralQuantileInterp = Literal["linear", "lower", "higher", "midpoint", "nearest"]
|
121
127
|
LiteralBizDayFreq = Literal["B", "BME", "BQE", "BYE"]
|
122
|
-
LiteralPandasReindexMethod =
|
123
|
-
None
|
124
|
-
|
125
|
-
"ffill",
|
126
|
-
"backfill",
|
127
|
-
"bfill",
|
128
|
-
"nearest",
|
129
|
-
]
|
128
|
+
LiteralPandasReindexMethod = (
|
129
|
+
Literal["pad", "ffill", "backfill", "bfill", "nearest"] | None
|
130
|
+
)
|
130
131
|
LiteralNanMethod = Literal["fill", "drop"]
|
131
132
|
LiteralCaptureRatio = Literal["up", "down", "both"]
|
132
133
|
LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"]
|