openseries 1.8.0__py3-none-any.whl → 1.8.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 +2 -1
- openseries/_common_model.py +107 -82
- openseries/_risk.py +2 -2
- openseries/datefixer.py +10 -4
- openseries/frame.py +89 -71
- openseries/load_plotly.py +4 -2
- openseries/owntypes.py +64 -3
- openseries/plotly_layouts.json +3 -3
- openseries/portfoliotools.py +19 -16
- openseries/series.py +41 -53
- openseries/simulation.py +5 -4
- openseries-1.8.1.dist-info/LICENSE.md +27 -0
- {openseries-1.8.0.dist-info → openseries-1.8.1.dist-info}/METADATA +41 -13
- openseries-1.8.1.dist-info/RECORD +16 -0
- {openseries-1.8.0.dist-info → openseries-1.8.1.dist-info}/WHEEL +1 -1
- openseries-1.8.0.dist-info/LICENSE.md +0 -27
- openseries-1.8.0.dist-info/RECORD +0 -16
openseries/frame.py
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
"""Defining the OpenFrame class."""
|
2
2
|
|
3
|
-
# mypy: disable-error-code="index,assignment,arg-type"
|
3
|
+
# mypy: disable-error-code="index,assignment,arg-type,no-any-return"
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
6
|
from copy import deepcopy
|
7
7
|
from functools import reduce
|
8
|
-
from logging import
|
8
|
+
from logging import getLogger
|
9
9
|
from typing import TYPE_CHECKING, cast
|
10
10
|
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
import datetime as dt
|
11
|
+
if TYPE_CHECKING: # pragma: no cover
|
12
|
+
import datetime as dt
|
13
|
+
|
14
|
+
from statsmodels.regression.linear_model import ( # type: ignore[import-untyped,unused-ignore]
|
15
|
+
OLSResults,
|
16
|
+
)
|
13
17
|
|
14
18
|
import statsmodels.api as sm # type: ignore[import-untyped,unused-ignore]
|
15
19
|
from numpy import (
|
@@ -35,17 +39,12 @@ from pandas import (
|
|
35
39
|
)
|
36
40
|
from pydantic import field_validator
|
37
41
|
|
38
|
-
# noinspection PyProtectedMember
|
39
|
-
from statsmodels.regression.linear_model import ( # type: ignore[import-untyped,unused-ignore]
|
40
|
-
OLSResults,
|
41
|
-
)
|
42
|
-
from typing_extensions import Self
|
43
|
-
|
44
42
|
from ._common_model import _CommonModel
|
45
43
|
from .datefixer import _do_resample_to_business_period_ends
|
46
44
|
from .owntypes import (
|
47
45
|
CountriesType,
|
48
46
|
DaysInYearType,
|
47
|
+
LabelsNotUniqueError,
|
49
48
|
LiteralBizDayFreq,
|
50
49
|
LiteralCaptureRatio,
|
51
50
|
LiteralFrameProps,
|
@@ -55,11 +54,18 @@ from .owntypes import (
|
|
55
54
|
LiteralPandasReindexMethod,
|
56
55
|
LiteralPortfolioWeightings,
|
57
56
|
LiteralTrunc,
|
57
|
+
MergingResultedInEmptyError,
|
58
|
+
MixedValuetypesError,
|
59
|
+
NoWeightsError,
|
58
60
|
OpenFramePropertiesList,
|
61
|
+
RatioInputError,
|
62
|
+
Self,
|
59
63
|
ValueType,
|
60
64
|
)
|
61
65
|
from .series import OpenTimeSeries
|
62
66
|
|
67
|
+
logger = getLogger(__name__)
|
68
|
+
|
63
69
|
__all__ = ["OpenFrame"]
|
64
70
|
|
65
71
|
|
@@ -88,8 +94,8 @@ class OpenFrame(_CommonModel):
|
|
88
94
|
weights: list[float] | None = None
|
89
95
|
|
90
96
|
# noinspection PyMethodParameters
|
91
|
-
@field_validator("constituents")
|
92
|
-
def _check_labels_unique(
|
97
|
+
@field_validator("constituents")
|
98
|
+
def _check_labels_unique( # type: ignore[misc]
|
93
99
|
cls: OpenFrame, # noqa: N805
|
94
100
|
tseries: list[OpenTimeSeries],
|
95
101
|
) -> list[OpenTimeSeries]:
|
@@ -97,7 +103,7 @@ class OpenFrame(_CommonModel):
|
|
97
103
|
labls = [x.label for x in tseries]
|
98
104
|
if len(set(labls)) != len(labls):
|
99
105
|
msg = "TimeSeries names/labels must be unique"
|
100
|
-
raise
|
106
|
+
raise LabelsNotUniqueError(msg)
|
101
107
|
return tseries
|
102
108
|
|
103
109
|
def __init__(
|
@@ -139,7 +145,7 @@ class OpenFrame(_CommonModel):
|
|
139
145
|
[x.tsdf for x in self.constituents],
|
140
146
|
)
|
141
147
|
else:
|
142
|
-
warning("OpenFrame() was passed an empty list.")
|
148
|
+
logger.warning("OpenFrame() was passed an empty list.")
|
143
149
|
|
144
150
|
def from_deepcopy(self: Self) -> Self:
|
145
151
|
"""Create copy of the OpenFrame object.
|
@@ -189,7 +195,7 @@ class OpenFrame(_CommonModel):
|
|
189
195
|
"Merging OpenTimeSeries DataFrames with "
|
190
196
|
f"argument how={how} produced an empty DataFrame."
|
191
197
|
)
|
192
|
-
raise
|
198
|
+
raise MergingResultedInEmptyError(msg)
|
193
199
|
|
194
200
|
if how == "inner":
|
195
201
|
for xerie in self.constituents:
|
@@ -220,7 +226,7 @@ class OpenFrame(_CommonModel):
|
|
220
226
|
prop_list = [
|
221
227
|
getattr(self, x) for x in OpenFramePropertiesList.allowed_strings
|
222
228
|
]
|
223
|
-
return cast(DataFrame, concat(prop_list, axis="columns").T)
|
229
|
+
return cast("DataFrame", concat(prop_list, axis="columns").T)
|
224
230
|
|
225
231
|
@property
|
226
232
|
def lengths_of_items(self: Self) -> Series[int]:
|
@@ -384,7 +390,7 @@ class OpenFrame(_CommonModel):
|
|
384
390
|
returns.iloc[0] = 0
|
385
391
|
else:
|
386
392
|
msg = "Mix of series types will give inconsistent results"
|
387
|
-
raise
|
393
|
+
raise MixedValuetypesError(msg)
|
388
394
|
|
389
395
|
returns = returns.add(1.0)
|
390
396
|
self.tsdf = returns.cumprod(axis=0) / returns.iloc[0]
|
@@ -521,23 +527,25 @@ class OpenFrame(_CommonModel):
|
|
521
527
|
if periods_in_a_year_fixed is None:
|
522
528
|
fraction = (later - earlier).days / 365.25
|
523
529
|
how_many = (
|
524
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
530
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
531
|
+
.count()
|
532
|
+
.iloc[0]
|
525
533
|
)
|
526
534
|
time_factor = how_many / fraction
|
527
535
|
else:
|
528
536
|
time_factor = periods_in_a_year_fixed
|
529
537
|
|
530
538
|
corr_label = (
|
531
|
-
cast(tuple[str, str], self.tsdf.iloc[:, first_column].name)[0]
|
539
|
+
cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0]
|
532
540
|
+ "_VS_"
|
533
|
-
+ cast(tuple[str, str], self.tsdf.iloc[:, second_column].name)[0]
|
541
|
+
+ cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0]
|
534
542
|
)
|
535
543
|
cols = [
|
536
|
-
cast(tuple[str, str], self.tsdf.iloc[:, first_column].name)[0],
|
537
|
-
cast(tuple[str, str], self.tsdf.iloc[:, second_column].name)[0],
|
544
|
+
cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0],
|
545
|
+
cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0],
|
538
546
|
]
|
539
547
|
|
540
|
-
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy()
|
548
|
+
data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
|
541
549
|
|
542
550
|
for rtn in cols:
|
543
551
|
data[rtn, ValueType.RTRN] = (
|
@@ -709,14 +717,14 @@ class OpenFrame(_CommonModel):
|
|
709
717
|
f"not truncated to same start dates.\n"
|
710
718
|
f"{self.tsdf.head()}"
|
711
719
|
)
|
712
|
-
warning(msg=msg)
|
720
|
+
logger.warning(msg=msg)
|
713
721
|
if len(set(self.last_indices)) != 1:
|
714
722
|
msg = (
|
715
723
|
f"One or more constituents still "
|
716
724
|
f"not truncated to same end dates.\n"
|
717
725
|
f"{self.tsdf.tail()}"
|
718
726
|
)
|
719
|
-
warning(msg=msg)
|
727
|
+
logger.warning(msg=msg)
|
720
728
|
return self
|
721
729
|
|
722
730
|
def relative(
|
@@ -740,9 +748,9 @@ class OpenFrame(_CommonModel):
|
|
740
748
|
|
741
749
|
"""
|
742
750
|
rel_label = (
|
743
|
-
cast(tuple[str, str], self.tsdf.iloc[:, long_column].name)[0]
|
751
|
+
cast("tuple[str, str]", self.tsdf.iloc[:, long_column].name)[0]
|
744
752
|
+ "_over_"
|
745
|
-
+ cast(tuple[str, str], self.tsdf.iloc[:, short_column].name)[0]
|
753
|
+
+ cast("tuple[str, str]", self.tsdf.iloc[:, short_column].name)[0]
|
746
754
|
)
|
747
755
|
if base_zero:
|
748
756
|
self.tsdf[rel_label, ValueType.RELRTRN] = (
|
@@ -796,17 +804,17 @@ class OpenFrame(_CommonModel):
|
|
796
804
|
|
797
805
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
798
806
|
if isinstance(base_column, tuple):
|
799
|
-
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
807
|
+
shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
|
800
808
|
:,
|
801
809
|
base_column,
|
802
810
|
]
|
803
811
|
short_item = base_column
|
804
812
|
short_label = cast(
|
805
|
-
tuple[str, ValueType],
|
813
|
+
"tuple[str, ValueType]",
|
806
814
|
self.tsdf.loc[:, base_column].name,
|
807
815
|
)[0]
|
808
816
|
elif isinstance(base_column, int):
|
809
|
-
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].iloc[
|
817
|
+
shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
|
810
818
|
:,
|
811
819
|
base_column,
|
812
820
|
]
|
@@ -814,7 +822,9 @@ class OpenFrame(_CommonModel):
|
|
814
822
|
:,
|
815
823
|
base_column,
|
816
824
|
].name
|
817
|
-
short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[
|
825
|
+
short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
|
826
|
+
0
|
827
|
+
]
|
818
828
|
else:
|
819
829
|
raise TypeError(msg)
|
820
830
|
|
@@ -828,7 +838,7 @@ class OpenFrame(_CommonModel):
|
|
828
838
|
if item == short_item:
|
829
839
|
terrors.append(0.0)
|
830
840
|
else:
|
831
|
-
longdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
841
|
+
longdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
|
832
842
|
:,
|
833
843
|
item,
|
834
844
|
]
|
@@ -886,17 +896,17 @@ class OpenFrame(_CommonModel):
|
|
886
896
|
|
887
897
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
888
898
|
if isinstance(base_column, tuple):
|
889
|
-
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
899
|
+
shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
|
890
900
|
:,
|
891
901
|
base_column,
|
892
902
|
]
|
893
903
|
short_item = base_column
|
894
904
|
short_label = cast(
|
895
|
-
tuple[str, str],
|
905
|
+
"tuple[str, str]",
|
896
906
|
self.tsdf.loc[:, base_column].name,
|
897
907
|
)[0]
|
898
908
|
elif isinstance(base_column, int):
|
899
|
-
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].iloc[
|
909
|
+
shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
|
900
910
|
:,
|
901
911
|
base_column,
|
902
912
|
]
|
@@ -904,7 +914,9 @@ class OpenFrame(_CommonModel):
|
|
904
914
|
:,
|
905
915
|
base_column,
|
906
916
|
].name
|
907
|
-
short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[
|
917
|
+
short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
|
918
|
+
0
|
919
|
+
]
|
908
920
|
else:
|
909
921
|
raise TypeError(msg)
|
910
922
|
|
@@ -918,7 +930,7 @@ class OpenFrame(_CommonModel):
|
|
918
930
|
if item == short_item:
|
919
931
|
ratios.append(0.0)
|
920
932
|
else:
|
921
|
-
longdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
933
|
+
longdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
|
922
934
|
:,
|
923
935
|
item,
|
924
936
|
]
|
@@ -938,7 +950,7 @@ class OpenFrame(_CommonModel):
|
|
938
950
|
dtype="float64",
|
939
951
|
)
|
940
952
|
|
941
|
-
def capture_ratio_func(
|
953
|
+
def capture_ratio_func(
|
942
954
|
self: Self,
|
943
955
|
ratio: LiteralCaptureRatio,
|
944
956
|
base_column: tuple[str, ValueType] | int = -1,
|
@@ -987,17 +999,17 @@ class OpenFrame(_CommonModel):
|
|
987
999
|
|
988
1000
|
msg = "base_column should be a tuple[str, ValueType] or an integer."
|
989
1001
|
if isinstance(base_column, tuple):
|
990
|
-
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
1002
|
+
shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
|
991
1003
|
:,
|
992
1004
|
base_column,
|
993
1005
|
]
|
994
1006
|
short_item = base_column
|
995
1007
|
short_label = cast(
|
996
|
-
tuple[str, str],
|
1008
|
+
"tuple[str, str]",
|
997
1009
|
self.tsdf.loc[:, base_column].name,
|
998
1010
|
)[0]
|
999
1011
|
elif isinstance(base_column, int):
|
1000
|
-
shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].iloc[
|
1012
|
+
shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
|
1001
1013
|
:,
|
1002
1014
|
base_column,
|
1003
1015
|
]
|
@@ -1005,7 +1017,9 @@ class OpenFrame(_CommonModel):
|
|
1005
1017
|
:,
|
1006
1018
|
base_column,
|
1007
1019
|
].name
|
1008
|
-
short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[
|
1020
|
+
short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
|
1021
|
+
0
|
1022
|
+
]
|
1009
1023
|
else:
|
1010
1024
|
raise TypeError(msg)
|
1011
1025
|
|
@@ -1019,7 +1033,7 @@ class OpenFrame(_CommonModel):
|
|
1019
1033
|
if item == short_item:
|
1020
1034
|
ratios.append(0.0)
|
1021
1035
|
else:
|
1022
|
-
longdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
|
1036
|
+
longdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
|
1023
1037
|
:,
|
1024
1038
|
item,
|
1025
1039
|
]
|
@@ -1119,7 +1133,7 @@ class OpenFrame(_CommonModel):
|
|
1119
1133
|
(up_rtrn / up_idx_return) / (down_return / down_idx_return),
|
1120
1134
|
)
|
1121
1135
|
else:
|
1122
|
-
raise
|
1136
|
+
raise RatioInputError(msg)
|
1123
1137
|
|
1124
1138
|
if ratio == "up":
|
1125
1139
|
resultname = f"Up Capture Ratios vs {short_label}"
|
@@ -1188,7 +1202,7 @@ class OpenFrame(_CommonModel):
|
|
1188
1202
|
)
|
1189
1203
|
elif isinstance(asset, int):
|
1190
1204
|
y_value = log(
|
1191
|
-
self.tsdf.iloc[:, asset] / cast(float, self.tsdf.iloc[0, asset]),
|
1205
|
+
self.tsdf.iloc[:, asset] / cast("float", self.tsdf.iloc[0, asset]),
|
1192
1206
|
)
|
1193
1207
|
else:
|
1194
1208
|
raise TypeError(msg)
|
@@ -1200,7 +1214,8 @@ class OpenFrame(_CommonModel):
|
|
1200
1214
|
)
|
1201
1215
|
elif isinstance(market, int):
|
1202
1216
|
x_value = log(
|
1203
|
-
self.tsdf.iloc[:, market]
|
1217
|
+
self.tsdf.iloc[:, market]
|
1218
|
+
/ cast("float", self.tsdf.iloc[0, market]),
|
1204
1219
|
)
|
1205
1220
|
else:
|
1206
1221
|
raise TypeError(msg)
|
@@ -1248,12 +1263,12 @@ class OpenFrame(_CommonModel):
|
|
1248
1263
|
if isinstance(y_column, tuple):
|
1249
1264
|
y_value = self.tsdf.loc[:, y_column]
|
1250
1265
|
y_label = cast(
|
1251
|
-
tuple[str, str],
|
1266
|
+
"tuple[str, str]",
|
1252
1267
|
self.tsdf.loc[:, y_column].name,
|
1253
1268
|
)[0]
|
1254
1269
|
elif isinstance(y_column, int):
|
1255
1270
|
y_value = self.tsdf.iloc[:, y_column]
|
1256
|
-
y_label = cast(tuple[str, str], self.tsdf.iloc[:, y_column].name)[0]
|
1271
|
+
y_label = cast("tuple[str, str]", self.tsdf.iloc[:, y_column].name)[0]
|
1257
1272
|
else:
|
1258
1273
|
raise TypeError(msg)
|
1259
1274
|
|
@@ -1261,12 +1276,12 @@ class OpenFrame(_CommonModel):
|
|
1261
1276
|
if isinstance(x_column, tuple):
|
1262
1277
|
x_value = self.tsdf.loc[:, x_column]
|
1263
1278
|
x_label = cast(
|
1264
|
-
tuple[str, str],
|
1279
|
+
"tuple[str, str]",
|
1265
1280
|
self.tsdf.loc[:, x_column].name,
|
1266
1281
|
)[0]
|
1267
1282
|
elif isinstance(x_column, int):
|
1268
1283
|
x_value = self.tsdf.iloc[:, x_column]
|
1269
|
-
x_label = cast(tuple[str, str], self.tsdf.iloc[:, x_column].name)[0]
|
1284
|
+
x_label = cast("tuple[str, str]", self.tsdf.iloc[:, x_column].name)[0]
|
1270
1285
|
else:
|
1271
1286
|
raise TypeError(msg)
|
1272
1287
|
|
@@ -1274,9 +1289,9 @@ class OpenFrame(_CommonModel):
|
|
1274
1289
|
if fitted_series:
|
1275
1290
|
self.tsdf[y_label, x_label] = results.predict(x_value)
|
1276
1291
|
|
1277
|
-
return cast(OLSResults, results)
|
1292
|
+
return cast("OLSResults", results)
|
1278
1293
|
|
1279
|
-
def jensen_alpha(
|
1294
|
+
def jensen_alpha(
|
1280
1295
|
self: Self,
|
1281
1296
|
asset: tuple[str, ValueType] | int,
|
1282
1297
|
market: tuple[str, ValueType] | int,
|
@@ -1330,17 +1345,17 @@ class OpenFrame(_CommonModel):
|
|
1330
1345
|
)
|
1331
1346
|
elif isinstance(asset, int):
|
1332
1347
|
asset_log = log(
|
1333
|
-
self.tsdf.iloc[:, asset] / cast(float, self.tsdf.iloc[0, asset]),
|
1348
|
+
self.tsdf.iloc[:, asset] / cast("float", self.tsdf.iloc[0, asset]),
|
1334
1349
|
)
|
1335
1350
|
if self.yearfrac > full_year:
|
1336
1351
|
asset_cagr = (
|
1337
|
-
cast(float, self.tsdf.iloc[-1, asset])
|
1338
|
-
/ cast(float, self.tsdf.iloc[0, asset])
|
1352
|
+
cast("float", self.tsdf.iloc[-1, asset])
|
1353
|
+
/ cast("float", self.tsdf.iloc[0, asset])
|
1339
1354
|
) ** (1 / self.yearfrac) - 1
|
1340
1355
|
else:
|
1341
1356
|
asset_cagr = (
|
1342
|
-
cast(float, self.tsdf.iloc[-1, asset])
|
1343
|
-
/ cast(float, self.tsdf.iloc[0, asset])
|
1357
|
+
cast("float", self.tsdf.iloc[-1, asset])
|
1358
|
+
/ cast("float", self.tsdf.iloc[0, asset])
|
1344
1359
|
- 1
|
1345
1360
|
)
|
1346
1361
|
else:
|
@@ -1364,17 +1379,18 @@ class OpenFrame(_CommonModel):
|
|
1364
1379
|
)
|
1365
1380
|
elif isinstance(market, int):
|
1366
1381
|
market_log = log(
|
1367
|
-
self.tsdf.iloc[:, market]
|
1382
|
+
self.tsdf.iloc[:, market]
|
1383
|
+
/ cast("float", self.tsdf.iloc[0, market]),
|
1368
1384
|
)
|
1369
1385
|
if self.yearfrac > full_year:
|
1370
1386
|
market_cagr = (
|
1371
|
-
cast(float, self.tsdf.iloc[-1, market])
|
1372
|
-
/ cast(float, self.tsdf.iloc[0, market])
|
1387
|
+
cast("float", self.tsdf.iloc[-1, market])
|
1388
|
+
/ cast("float", self.tsdf.iloc[0, market])
|
1373
1389
|
) ** (1 / self.yearfrac) - 1
|
1374
1390
|
else:
|
1375
1391
|
market_cagr = (
|
1376
|
-
cast(float, self.tsdf.iloc[-1, market])
|
1377
|
-
/ cast(float, self.tsdf.iloc[0, market])
|
1392
|
+
cast("float", self.tsdf.iloc[-1, market])
|
1393
|
+
/ cast("float", self.tsdf.iloc[0, market])
|
1378
1394
|
- 1
|
1379
1395
|
)
|
1380
1396
|
else:
|
@@ -1401,7 +1417,7 @@ class OpenFrame(_CommonModel):
|
|
1401
1417
|
raise TypeError(msg)
|
1402
1418
|
else:
|
1403
1419
|
msg = "Mix of series types will give inconsistent results"
|
1404
|
-
raise
|
1420
|
+
raise MixedValuetypesError(msg)
|
1405
1421
|
|
1406
1422
|
covariance = cov(asset_log, market_log, ddof=dlta_degr_freedms)
|
1407
1423
|
beta = covariance[0, 1] / covariance[1, 1]
|
@@ -1433,7 +1449,7 @@ class OpenFrame(_CommonModel):
|
|
1433
1449
|
"OpenFrame weights property must be provided "
|
1434
1450
|
"to run the make_portfolio method."
|
1435
1451
|
)
|
1436
|
-
raise
|
1452
|
+
raise NoWeightsError(msg)
|
1437
1453
|
|
1438
1454
|
vtypes = [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)]
|
1439
1455
|
if not any(vtypes):
|
@@ -1443,7 +1459,7 @@ class OpenFrame(_CommonModel):
|
|
1443
1459
|
returns = self.tsdf.copy()
|
1444
1460
|
else:
|
1445
1461
|
msg = "Mix of series types will give inconsistent results"
|
1446
|
-
raise
|
1462
|
+
raise MixedValuetypesError(msg)
|
1447
1463
|
|
1448
1464
|
msg = "Weight strategy not implemented"
|
1449
1465
|
if weight_strat:
|
@@ -1494,11 +1510,11 @@ class OpenFrame(_CommonModel):
|
|
1494
1510
|
|
1495
1511
|
"""
|
1496
1512
|
long_label = cast(
|
1497
|
-
tuple[str, str],
|
1513
|
+
"tuple[str, str]",
|
1498
1514
|
self.tsdf.iloc[:, long_column].name,
|
1499
1515
|
)[0]
|
1500
1516
|
short_label = cast(
|
1501
|
-
tuple[str, str],
|
1517
|
+
"tuple[str, str]",
|
1502
1518
|
self.tsdf.iloc[:, short_column].name,
|
1503
1519
|
)[0]
|
1504
1520
|
ratio_label = f"{long_label} / {short_label}"
|
@@ -1559,8 +1575,10 @@ class OpenFrame(_CommonModel):
|
|
1559
1575
|
Rolling Betas
|
1560
1576
|
|
1561
1577
|
"""
|
1562
|
-
market_label = cast(tuple[str, str], self.tsdf.iloc[:, market_column].name)[
|
1563
|
-
|
1578
|
+
market_label = cast("tuple[str, str]", self.tsdf.iloc[:, market_column].name)[
|
1579
|
+
0
|
1580
|
+
]
|
1581
|
+
asset_label = cast("tuple[str, str]", self.tsdf.iloc[:, asset_column].name)[0]
|
1564
1582
|
beta_label = f"{asset_label} / {market_label}"
|
1565
1583
|
|
1566
1584
|
rolling = (
|
@@ -1618,9 +1636,9 @@ class OpenFrame(_CommonModel):
|
|
1618
1636
|
|
1619
1637
|
"""
|
1620
1638
|
corr_label = (
|
1621
|
-
cast(tuple[str, str], self.tsdf.iloc[:, first_column].name)[0]
|
1639
|
+
cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0]
|
1622
1640
|
+ "_VS_"
|
1623
|
-
+ cast(tuple[str, str], self.tsdf.iloc[:, second_column].name)[0]
|
1641
|
+
+ cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0]
|
1624
1642
|
)
|
1625
1643
|
first_series = (
|
1626
1644
|
self.tsdf.iloc[:, first_column]
|
openseries/load_plotly.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from json import load
|
6
|
-
from logging import
|
6
|
+
from logging import getLogger
|
7
7
|
from pathlib import Path
|
8
8
|
from typing import TYPE_CHECKING
|
9
9
|
|
@@ -13,6 +13,8 @@ from requests.exceptions import ConnectionError as RequestsConnectionError
|
|
13
13
|
if TYPE_CHECKING:
|
14
14
|
from .owntypes import CaptorLogoType, PlotlyLayoutType # pragma: no cover
|
15
15
|
|
16
|
+
logger = getLogger(__name__)
|
17
|
+
|
16
18
|
__all__ = ["load_plotly_dict"]
|
17
19
|
|
18
20
|
|
@@ -69,7 +71,7 @@ def load_plotly_dict(
|
|
69
71
|
|
70
72
|
if _check_remote_file_existence(url=logo["source"]) is False:
|
71
73
|
msg = f"Failed to add logo image from URL {logo['source']}"
|
72
|
-
warning(msg)
|
74
|
+
logger.warning(msg)
|
73
75
|
logo = {}
|
74
76
|
|
75
77
|
fig["config"].update({"responsive": responsive})
|
openseries/owntypes.py
CHANGED
@@ -10,9 +10,14 @@ from typing import Annotated, ClassVar, Literal, Union
|
|
10
10
|
from numpy import datetime64
|
11
11
|
from pandas import Timestamp
|
12
12
|
from pydantic import BaseModel, Field, StringConstraints, conlist, conset
|
13
|
-
from typing_extensions import Self
|
14
13
|
|
15
|
-
|
14
|
+
try:
|
15
|
+
from typing import Self # type: ignore[attr-defined,unused-ignore]
|
16
|
+
except ImportError: # pragma: no cover
|
17
|
+
from typing_extensions import Self
|
18
|
+
|
19
|
+
|
20
|
+
__all__ = ["Self", "ValueType"]
|
16
21
|
|
17
22
|
|
18
23
|
CountryStringType = Annotated[
|
@@ -262,7 +267,7 @@ class PropertiesList(list[str]):
|
|
262
267
|
if len(duplicates) != 0:
|
263
268
|
msg += f"Duplicate string(s): {list(duplicates)}."
|
264
269
|
if len(msg) != 0:
|
265
|
-
raise
|
270
|
+
raise PropertiesInputValidationError(msg)
|
266
271
|
|
267
272
|
|
268
273
|
class OpenTimeSeriesPropertiesList(PropertiesList):
|
@@ -316,3 +321,59 @@ class ValueType(str, Enum):
|
|
316
321
|
ROLLRTRN = "Rolling returns"
|
317
322
|
ROLLVAR = "Rolling VaR"
|
318
323
|
ROLLVOL = "Rolling volatility"
|
324
|
+
|
325
|
+
|
326
|
+
class MixedValuetypesError(Exception):
|
327
|
+
"""Raised when provided timeseries valuetypes are not the same."""
|
328
|
+
|
329
|
+
|
330
|
+
class AtLeastOneFrameError(Exception):
|
331
|
+
"""Raised when none of the possible frame inputs is provided."""
|
332
|
+
|
333
|
+
|
334
|
+
class DateAlignmentError(Exception):
|
335
|
+
"""Raised when date input is not aligned with existing range."""
|
336
|
+
|
337
|
+
|
338
|
+
class NumberOfItemsAndLabelsNotSameError(Exception):
|
339
|
+
"""Raised when number of labels is not matching the number of timeseries."""
|
340
|
+
|
341
|
+
|
342
|
+
class InitialValueZeroError(Exception):
|
343
|
+
"""Raised when a calculation cannot be performed due to initial value(s) zero."""
|
344
|
+
|
345
|
+
|
346
|
+
class CountriesNotStringNorListStrError(Exception):
|
347
|
+
"""Raised when countries argument is not provided in correct format."""
|
348
|
+
|
349
|
+
|
350
|
+
class TradingDaysNotAboveZeroError(Exception):
|
351
|
+
"""Raised when trading days argument is not above zero."""
|
352
|
+
|
353
|
+
|
354
|
+
class BothStartAndEndError(Exception):
|
355
|
+
"""Raised when both start and end dates are provided."""
|
356
|
+
|
357
|
+
|
358
|
+
class NoWeightsError(Exception):
|
359
|
+
"""Raised when no weights are provided to function where necessary."""
|
360
|
+
|
361
|
+
|
362
|
+
class LabelsNotUniqueError(Exception):
|
363
|
+
"""Raised when provided label names are not unique."""
|
364
|
+
|
365
|
+
|
366
|
+
class RatioInputError(Exception):
|
367
|
+
"""Raised when ratio keyword not provided correctly."""
|
368
|
+
|
369
|
+
|
370
|
+
class MergingResultedInEmptyError(Exception):
|
371
|
+
"""Raised when a merge resulted in an empty DataFrame."""
|
372
|
+
|
373
|
+
|
374
|
+
class IncorrectArgumentComboError(Exception):
|
375
|
+
"""Raised when correct combination of arguments is not provided."""
|
376
|
+
|
377
|
+
|
378
|
+
class PropertiesInputValidationError(Exception):
|
379
|
+
"""Raised when duplicate strings are provided."""
|
openseries/plotly_layouts.json
CHANGED
@@ -54,14 +54,14 @@
|
|
54
54
|
"plot_bgcolor": "rgba(0, 0, 0, 0)",
|
55
55
|
"showlegend": true,
|
56
56
|
"title": {
|
57
|
+
"font": {
|
58
|
+
"size": 24
|
59
|
+
},
|
57
60
|
"x": 0.5,
|
58
61
|
"xanchor": "center",
|
59
62
|
"y": 0.95,
|
60
63
|
"yanchor": "top"
|
61
64
|
},
|
62
|
-
"titlefont": {
|
63
|
-
"size": 24
|
64
|
-
},
|
65
65
|
"xaxis": {
|
66
66
|
"gridcolor": "#EEEEEE",
|
67
67
|
"tickangle": -45,
|