openseries 1.9.5__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 +371 -442
- openseries/datefixer.py +4 -2
- openseries/frame.py +103 -113
- openseries/owntypes.py +48 -47
- openseries/portfoliotools.py +3 -4
- openseries/report.py +12 -14
- openseries/series.py +33 -19
- openseries/simulation.py +1 -1
- {openseries-1.9.5.dist-info → openseries-1.9.6.dist-info}/METADATA +4 -3
- openseries-1.9.6.dist-info/RECORD +17 -0
- {openseries-1.9.5.dist-info → openseries-1.9.6.dist-info}/WHEEL +1 -1
- openseries-1.9.5.dist-info/RECORD +0 -17
- {openseries-1.9.5.dist-info → openseries-1.9.6.dist-info/licenses}/LICENSE.md +0 -0
openseries/datefixer.py
CHANGED
@@ -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)
|
openseries/frame.py
CHANGED
@@ -14,14 +14,11 @@ from functools import reduce
|
|
14
14
|
from logging import getLogger
|
15
15
|
from typing import TYPE_CHECKING, Any, cast
|
16
16
|
|
17
|
-
if TYPE_CHECKING: # pragma: no cover
|
18
|
-
import datetime as dt
|
19
|
-
|
20
|
-
from numpy import dtype, int64, ndarray
|
21
|
-
|
22
17
|
from numpy import (
|
23
18
|
array,
|
19
|
+
concatenate,
|
24
20
|
cov,
|
21
|
+
diff,
|
25
22
|
divide,
|
26
23
|
isinf,
|
27
24
|
log,
|
@@ -38,6 +35,17 @@ from pandas import (
|
|
38
35
|
concat,
|
39
36
|
merge,
|
40
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
|
+
|
41
49
|
from pydantic import field_validator
|
42
50
|
from sklearn.linear_model import LinearRegression # type: ignore[import-untyped]
|
43
51
|
|
@@ -70,7 +78,7 @@ __all__ = ["OpenFrame"]
|
|
70
78
|
|
71
79
|
|
72
80
|
# noinspection PyUnresolvedReferences,PyTypeChecker
|
73
|
-
class OpenFrame(_CommonModel):
|
81
|
+
class OpenFrame(_CommonModel[SeriesFloat]):
|
74
82
|
"""OpenFrame objects hold OpenTimeSeries in the list constituents.
|
75
83
|
|
76
84
|
The intended use is to allow comparisons across these timeseries.
|
@@ -94,9 +102,9 @@ class OpenFrame(_CommonModel):
|
|
94
102
|
weights: list[float] | None = None
|
95
103
|
|
96
104
|
# noinspection PyMethodParameters
|
97
|
-
@field_validator("constituents")
|
105
|
+
@field_validator("constituents")
|
98
106
|
def _check_labels_unique(
|
99
|
-
cls: OpenFrame, # noqa: N805
|
107
|
+
cls: type[OpenFrame], # noqa: N805
|
100
108
|
tseries: list[OpenTimeSeries],
|
101
109
|
) -> list[OpenTimeSeries]:
|
102
110
|
"""Pydantic validator ensuring that OpenFrame labels are unique."""
|
@@ -130,7 +138,7 @@ class OpenFrame(_CommonModel):
|
|
130
138
|
"""
|
131
139
|
copied_constituents = [ts.from_deepcopy() for ts in constituents]
|
132
140
|
|
133
|
-
super().__init__(
|
141
|
+
super().__init__( # type: ignore[call-arg]
|
134
142
|
constituents=copied_constituents,
|
135
143
|
weights=weights,
|
136
144
|
)
|
@@ -241,7 +249,7 @@ class OpenFrame(_CommonModel):
|
|
241
249
|
|
242
250
|
"""
|
243
251
|
return Series(
|
244
|
-
data=[self.tsdf
|
252
|
+
data=[self.tsdf[col].count() for col in self.tsdf.columns],
|
245
253
|
index=self.tsdf.columns,
|
246
254
|
name="observations",
|
247
255
|
).astype(int)
|
@@ -557,12 +565,14 @@ class OpenFrame(_CommonModel):
|
|
557
565
|
|
558
566
|
"""
|
559
567
|
earlier, later = self.calc_range(
|
560
|
-
months_offset=months_from_last,
|
568
|
+
months_offset=months_from_last,
|
569
|
+
from_dt=from_date,
|
570
|
+
to_dt=to_date,
|
561
571
|
)
|
562
572
|
if periods_in_a_year_fixed is None:
|
563
573
|
fraction = (later - earlier).days / 365.25
|
564
574
|
how_many = (
|
565
|
-
self.tsdf.loc[cast("
|
575
|
+
self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
|
566
576
|
.count()
|
567
577
|
.iloc[0]
|
568
578
|
)
|
@@ -580,33 +590,36 @@ class OpenFrame(_CommonModel):
|
|
580
590
|
cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0],
|
581
591
|
]
|
582
592
|
|
583
|
-
data = self.tsdf.loc[
|
593
|
+
data = self.tsdf.loc[
|
594
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
595
|
+
].copy()
|
584
596
|
|
585
597
|
for rtn in cols:
|
586
|
-
|
598
|
+
arr = concatenate([array([nan]), diff(log(data[(rtn, ValueType.PRICE)]))])
|
599
|
+
data[rtn, ValueType.RTRN] = arr
|
587
600
|
|
588
601
|
raw_one = [
|
589
|
-
data
|
602
|
+
data[(cols[0], ValueType.RTRN)]
|
590
603
|
.iloc[1:day_chunk]
|
591
604
|
.std(ddof=dlta_degr_freedms)
|
592
605
|
* sqrt(time_factor),
|
593
606
|
]
|
594
607
|
raw_two = [
|
595
|
-
data
|
608
|
+
data[(cols[1], ValueType.RTRN)]
|
596
609
|
.iloc[1:day_chunk]
|
597
610
|
.std(ddof=dlta_degr_freedms)
|
598
611
|
* sqrt(time_factor),
|
599
612
|
]
|
600
613
|
raw_cov = [
|
601
614
|
cov(
|
602
|
-
m=data
|
603
|
-
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(),
|
604
617
|
ddof=dlta_degr_freedms,
|
605
618
|
)[0][1],
|
606
619
|
]
|
607
620
|
|
608
|
-
r1 = data
|
609
|
-
r2 = data
|
621
|
+
r1 = data[(cols[0], ValueType.RTRN)]
|
622
|
+
r2 = data[(cols[1], ValueType.RTRN)]
|
610
623
|
|
611
624
|
alpha = 1.0 - lmbda
|
612
625
|
|
@@ -838,30 +851,29 @@ class OpenFrame(_CommonModel):
|
|
838
851
|
|
839
852
|
"""
|
840
853
|
earlier, later = self.calc_range(
|
841
|
-
months_offset=months_from_last,
|
854
|
+
months_offset=months_from_last,
|
855
|
+
from_dt=from_date,
|
856
|
+
to_dt=to_date,
|
842
857
|
)
|
843
|
-
fraction = (later - earlier).days / 365.25
|
858
|
+
fraction: float = (later - earlier).days / 365.25
|
844
859
|
|
845
860
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
846
861
|
if isinstance(base_column, tuple):
|
847
|
-
shortdf = self.tsdf.loc[
|
848
|
-
|
849
|
-
|
850
|
-
]
|
862
|
+
shortdf = self.tsdf.loc[
|
863
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
864
|
+
][base_column]
|
851
865
|
short_item = base_column
|
852
866
|
short_label = cast(
|
853
867
|
"tuple[str, ValueType]",
|
854
|
-
self.tsdf
|
868
|
+
self.tsdf[base_column].name,
|
855
869
|
)[0]
|
856
870
|
elif isinstance(base_column, int):
|
857
|
-
shortdf =
|
858
|
-
"
|
859
|
-
|
860
|
-
:, base_column
|
861
|
-
],
|
862
|
-
)
|
871
|
+
shortdf = self.tsdf.loc[
|
872
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
873
|
+
].iloc[:, base_column]
|
863
874
|
short_item = cast(
|
864
|
-
"tuple[str, ValueType]",
|
875
|
+
"tuple[str, ValueType]",
|
876
|
+
self.tsdf.iloc[:, base_column].name,
|
865
877
|
)
|
866
878
|
short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
|
867
879
|
0
|
@@ -872,17 +884,16 @@ class OpenFrame(_CommonModel):
|
|
872
884
|
if periods_in_a_year_fixed:
|
873
885
|
time_factor = float(periods_in_a_year_fixed)
|
874
886
|
else:
|
875
|
-
time_factor =
|
887
|
+
time_factor = shortdf.count() / fraction
|
876
888
|
|
877
889
|
terrors = []
|
878
890
|
for item in self.tsdf:
|
879
891
|
if item == short_item:
|
880
892
|
terrors.append(0.0)
|
881
893
|
else:
|
882
|
-
longdf = self.tsdf.loc[
|
883
|
-
|
884
|
-
|
885
|
-
]
|
894
|
+
longdf = self.tsdf.loc[
|
895
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
896
|
+
][item]
|
886
897
|
relative = longdf.ffill().pct_change() - shortdf.ffill().pct_change()
|
887
898
|
vol = float(relative.std() * sqrt(time_factor))
|
888
899
|
terrors.append(vol)
|
@@ -931,29 +942,26 @@ class OpenFrame(_CommonModel):
|
|
931
942
|
|
932
943
|
"""
|
933
944
|
earlier, later = self.calc_range(
|
934
|
-
months_offset=months_from_last,
|
945
|
+
months_offset=months_from_last,
|
946
|
+
from_dt=from_date,
|
947
|
+
to_dt=to_date,
|
935
948
|
)
|
936
|
-
fraction = (later - earlier).days / 365.25
|
949
|
+
fraction: float = (later - earlier).days / 365.25
|
937
950
|
|
938
951
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
939
952
|
if isinstance(base_column, tuple):
|
940
|
-
shortdf = self.tsdf.loc[
|
941
|
-
|
942
|
-
|
943
|
-
]
|
953
|
+
shortdf = self.tsdf.loc[
|
954
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
955
|
+
][base_column]
|
944
956
|
short_item = base_column
|
945
957
|
short_label = cast(
|
946
958
|
"tuple[str, str]",
|
947
|
-
self.tsdf
|
959
|
+
self.tsdf[base_column].name,
|
948
960
|
)[0]
|
949
961
|
elif isinstance(base_column, int):
|
950
|
-
shortdf =
|
951
|
-
"
|
952
|
-
|
953
|
-
:,
|
954
|
-
base_column,
|
955
|
-
],
|
956
|
-
)
|
962
|
+
shortdf = self.tsdf.loc[
|
963
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
964
|
+
].iloc[:, base_column]
|
957
965
|
short_item = cast(
|
958
966
|
"tuple[str, ValueType]",
|
959
967
|
self.tsdf.iloc[
|
@@ -970,17 +978,16 @@ class OpenFrame(_CommonModel):
|
|
970
978
|
if periods_in_a_year_fixed:
|
971
979
|
time_factor = float(periods_in_a_year_fixed)
|
972
980
|
else:
|
973
|
-
time_factor =
|
981
|
+
time_factor = shortdf.count() / fraction
|
974
982
|
|
975
983
|
ratios = []
|
976
984
|
for item in self.tsdf:
|
977
985
|
if item == short_item:
|
978
986
|
ratios.append(0.0)
|
979
987
|
else:
|
980
|
-
longdf = self.tsdf.loc[
|
981
|
-
|
982
|
-
|
983
|
-
]
|
988
|
+
longdf = self.tsdf.loc[
|
989
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
990
|
+
][item]
|
984
991
|
relative = longdf.ffill().pct_change() - shortdf.ffill().pct_change()
|
985
992
|
ret = float(relative.mean() * time_factor)
|
986
993
|
vol = float(relative.std() * sqrt(time_factor))
|
@@ -1038,29 +1045,26 @@ class OpenFrame(_CommonModel):
|
|
1038
1045
|
"""
|
1039
1046
|
loss_limit: float = 0.0
|
1040
1047
|
earlier, later = self.calc_range(
|
1041
|
-
months_offset=months_from_last,
|
1048
|
+
months_offset=months_from_last,
|
1049
|
+
from_dt=from_date,
|
1050
|
+
to_dt=to_date,
|
1042
1051
|
)
|
1043
|
-
fraction = (later - earlier).days / 365.25
|
1052
|
+
fraction: float = (later - earlier).days / 365.25
|
1044
1053
|
|
1045
1054
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
1046
1055
|
if isinstance(base_column, tuple):
|
1047
|
-
shortdf = self.tsdf.loc[
|
1048
|
-
|
1049
|
-
|
1050
|
-
]
|
1056
|
+
shortdf = self.tsdf.loc[
|
1057
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
1058
|
+
][base_column]
|
1051
1059
|
short_item = base_column
|
1052
1060
|
short_label = cast(
|
1053
1061
|
"tuple[str, str]",
|
1054
|
-
self.tsdf
|
1062
|
+
self.tsdf[base_column].name,
|
1055
1063
|
)[0]
|
1056
1064
|
elif isinstance(base_column, int):
|
1057
|
-
shortdf =
|
1058
|
-
"
|
1059
|
-
|
1060
|
-
:,
|
1061
|
-
base_column,
|
1062
|
-
],
|
1063
|
-
)
|
1065
|
+
shortdf = self.tsdf.loc[
|
1066
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
1067
|
+
].iloc[:, base_column]
|
1064
1068
|
short_item = cast(
|
1065
1069
|
"tuple[str, ValueType]",
|
1066
1070
|
self.tsdf.iloc[
|
@@ -1077,17 +1081,16 @@ class OpenFrame(_CommonModel):
|
|
1077
1081
|
if periods_in_a_year_fixed:
|
1078
1082
|
time_factor = float(periods_in_a_year_fixed)
|
1079
1083
|
else:
|
1080
|
-
time_factor =
|
1084
|
+
time_factor = shortdf.count() / fraction
|
1081
1085
|
|
1082
1086
|
ratios = []
|
1083
1087
|
for item in self.tsdf:
|
1084
1088
|
if item == short_item:
|
1085
1089
|
ratios.append(0.0)
|
1086
1090
|
else:
|
1087
|
-
longdf = self.tsdf.loc[
|
1088
|
-
|
1089
|
-
|
1090
|
-
]
|
1091
|
+
longdf = self.tsdf.loc[
|
1092
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
1093
|
+
][item]
|
1091
1094
|
msg = "ratio must be one of 'up', 'down' or 'both'."
|
1092
1095
|
if ratio == "up":
|
1093
1096
|
uparray = (
|
@@ -1230,38 +1233,33 @@ class OpenFrame(_CommonModel):
|
|
1230
1233
|
if all(vtypes):
|
1231
1234
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1232
1235
|
if isinstance(asset, tuple):
|
1233
|
-
y_value = self.tsdf
|
1236
|
+
y_value = self.tsdf[asset]
|
1234
1237
|
elif isinstance(asset, int):
|
1235
|
-
y_value =
|
1238
|
+
y_value = self.tsdf.iloc[:, asset]
|
1236
1239
|
else:
|
1237
1240
|
raise TypeError(msg)
|
1238
1241
|
|
1239
1242
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1240
1243
|
if isinstance(market, tuple):
|
1241
|
-
x_value = self.tsdf
|
1244
|
+
x_value = self.tsdf[market]
|
1242
1245
|
elif isinstance(market, int):
|
1243
|
-
x_value =
|
1246
|
+
x_value = self.tsdf.iloc[:, market]
|
1244
1247
|
else:
|
1245
1248
|
raise TypeError(msg)
|
1246
1249
|
elif not any(vtypes):
|
1247
1250
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1248
1251
|
if isinstance(asset, tuple):
|
1249
|
-
y_value = self.tsdf
|
1252
|
+
y_value = self.tsdf[asset].ffill().pct_change().iloc[1:]
|
1250
1253
|
elif isinstance(asset, int):
|
1251
|
-
y_value =
|
1252
|
-
"DataFrame", self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
|
1253
|
-
)
|
1254
|
+
y_value = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
|
1254
1255
|
else:
|
1255
1256
|
raise TypeError(msg)
|
1256
1257
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1257
1258
|
|
1258
1259
|
if isinstance(market, tuple):
|
1259
|
-
x_value = self.tsdf
|
1260
|
+
x_value = self.tsdf[market].ffill().pct_change().iloc[1:]
|
1260
1261
|
elif isinstance(market, int):
|
1261
|
-
x_value =
|
1262
|
-
"DataFrame",
|
1263
|
-
self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:],
|
1264
|
-
)
|
1262
|
+
x_value = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
|
1265
1263
|
else:
|
1266
1264
|
raise TypeError(msg)
|
1267
1265
|
else:
|
@@ -1302,26 +1300,23 @@ class OpenFrame(_CommonModel):
|
|
1302
1300
|
"""
|
1303
1301
|
msg = "y_column should be a tuple[str, ValueType] or an integer."
|
1304
1302
|
if isinstance(y_column, tuple):
|
1305
|
-
y_value = self.tsdf
|
1303
|
+
y_value = self.tsdf[y_column].to_numpy()
|
1306
1304
|
y_label = cast(
|
1307
1305
|
"tuple[str, str]",
|
1308
|
-
self.tsdf
|
1306
|
+
self.tsdf[y_column].name,
|
1309
1307
|
)[0]
|
1310
1308
|
elif isinstance(y_column, int):
|
1311
|
-
y_value =
|
1312
|
-
"ndarray[tuple[int, int], dtype[Any]]",
|
1313
|
-
self.tsdf.iloc[:, y_column].to_numpy(),
|
1314
|
-
)
|
1309
|
+
y_value = self.tsdf.iloc[:, y_column].to_numpy()
|
1315
1310
|
y_label = cast("tuple[str, str]", self.tsdf.iloc[:, y_column].name)[0]
|
1316
1311
|
else:
|
1317
1312
|
raise TypeError(msg)
|
1318
1313
|
|
1319
1314
|
msg = "x_column should be a tuple[str, ValueType] or an integer."
|
1320
1315
|
if isinstance(x_column, tuple):
|
1321
|
-
x_value = self.tsdf
|
1316
|
+
x_value = self.tsdf[x_column].to_numpy().reshape(-1, 1)
|
1322
1317
|
x_label = cast(
|
1323
1318
|
"tuple[str, str]",
|
1324
|
-
self.tsdf
|
1319
|
+
self.tsdf[x_column].name,
|
1325
1320
|
)[0]
|
1326
1321
|
elif isinstance(x_column, int):
|
1327
1322
|
x_value = self.tsdf.iloc[:, x_column].to_numpy().reshape(-1, 1)
|
@@ -1376,45 +1371,40 @@ class OpenFrame(_CommonModel):
|
|
1376
1371
|
if not any(vtypes):
|
1377
1372
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1378
1373
|
if isinstance(asset, tuple):
|
1379
|
-
asset_rtn = self.tsdf
|
1374
|
+
asset_rtn = self.tsdf[asset].ffill().pct_change().iloc[1:]
|
1380
1375
|
asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
|
1381
1376
|
elif isinstance(asset, int):
|
1382
|
-
asset_rtn =
|
1383
|
-
"DataFrame", self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
|
1384
|
-
)
|
1377
|
+
asset_rtn = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
|
1385
1378
|
asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
|
1386
1379
|
else:
|
1387
1380
|
raise TypeError(msg)
|
1388
1381
|
|
1389
1382
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1390
1383
|
if isinstance(market, tuple):
|
1391
|
-
market_rtn = self.tsdf
|
1384
|
+
market_rtn = self.tsdf[market].ffill().pct_change().iloc[1:]
|
1392
1385
|
market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
|
1393
1386
|
elif isinstance(market, int):
|
1394
|
-
market_rtn =
|
1395
|
-
"DataFrame",
|
1396
|
-
self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:],
|
1397
|
-
)
|
1387
|
+
market_rtn = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
|
1398
1388
|
market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
|
1399
1389
|
else:
|
1400
1390
|
raise TypeError(msg)
|
1401
1391
|
elif all(vtypes):
|
1402
1392
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1403
1393
|
if isinstance(asset, tuple):
|
1404
|
-
asset_rtn = self.tsdf
|
1394
|
+
asset_rtn = self.tsdf[asset]
|
1405
1395
|
asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
|
1406
1396
|
elif isinstance(asset, int):
|
1407
|
-
asset_rtn =
|
1397
|
+
asset_rtn = self.tsdf.iloc[:, asset]
|
1408
1398
|
asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
|
1409
1399
|
else:
|
1410
1400
|
raise TypeError(msg)
|
1411
1401
|
|
1412
1402
|
msg = "market should be a tuple[str, ValueType] or an integer."
|
1413
1403
|
if isinstance(market, tuple):
|
1414
|
-
market_rtn = self.tsdf
|
1404
|
+
market_rtn = self.tsdf[market]
|
1415
1405
|
market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
|
1416
1406
|
elif isinstance(market, int):
|
1417
|
-
market_rtn =
|
1407
|
+
market_rtn = self.tsdf.iloc[:, market]
|
1418
1408
|
market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
|
1419
1409
|
else:
|
1420
1410
|
raise TypeError(msg)
|
@@ -1426,7 +1416,7 @@ class OpenFrame(_CommonModel):
|
|
1426
1416
|
beta = covariance[0, 1] / covariance[1, 1]
|
1427
1417
|
|
1428
1418
|
return float(
|
1429
|
-
asset_rtn_mean - riskfree_rate - beta * (market_rtn_mean - riskfree_rate)
|
1419
|
+
asset_rtn_mean - riskfree_rate - beta * (market_rtn_mean - riskfree_rate),
|
1430
1420
|
)
|
1431
1421
|
|
1432
1422
|
def make_portfolio(
|
@@ -1612,7 +1602,7 @@ class OpenFrame(_CommonModel):
|
|
1612
1602
|
rollbeta.index = rollbeta.index.droplevel(level=1)
|
1613
1603
|
rollbeta.columns = MultiIndex.from_arrays([[beta_label], ["Beta"]])
|
1614
1604
|
|
1615
|
-
return
|
1605
|
+
return rollbeta
|
1616
1606
|
|
1617
1607
|
def rolling_corr(
|
1618
1608
|
self: Self,
|
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"]
|
openseries/portfoliotools.py
CHANGED
@@ -366,7 +366,7 @@ def constrain_optimized_portfolios(
|
|
366
366
|
if not bounds:
|
367
367
|
bounds = tuple((0.0, 1.0) for _ in range(data.item_count))
|
368
368
|
|
369
|
-
front_frame,
|
369
|
+
front_frame, _, _ = efficient_frontier(
|
370
370
|
eframe=data,
|
371
371
|
num_ports=simulations,
|
372
372
|
frontier_points=curve_points,
|
@@ -440,14 +440,13 @@ def prepare_plot_data(
|
|
440
440
|
for wgt, nm in zip(optimized[3:], assets.columns_lvl_zero, strict=True)
|
441
441
|
]
|
442
442
|
opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list)
|
443
|
-
vol = cast("Series[float]", assets.vol)
|
444
443
|
plotframe = DataFrame(
|
445
444
|
data=[
|
446
445
|
assets.arithmetic_ret,
|
447
|
-
vol,
|
446
|
+
assets.vol,
|
448
447
|
Series(
|
449
448
|
data=[""] * assets.item_count,
|
450
|
-
index=vol.index,
|
449
|
+
index=assets.vol.index,
|
451
450
|
),
|
452
451
|
],
|
453
452
|
index=["ret", "stdev", "text"],
|