openseries 1.7.2__py3-none-any.whl → 1.7.3__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 +34 -46
- openseries/datefixer.py +3 -4
- openseries/frame.py +29 -43
- openseries/portfoliotools.py +2 -4
- openseries/series.py +5 -7
- openseries/simulation.py +4 -8
- openseries/types.py +35 -51
- {openseries-1.7.2.dist-info → openseries-1.7.3.dist-info}/METADATA +2 -2
- openseries-1.7.3.dist-info/RECORD +16 -0
- openseries-1.7.2.dist-info/RECORD +0 -16
- {openseries-1.7.2.dist-info → openseries-1.7.3.dist-info}/LICENSE.md +0 -0
- {openseries-1.7.2.dist-info → openseries-1.7.3.dist-info}/WHEEL +0 -0
openseries/_common_model.py
CHANGED
@@ -18,7 +18,15 @@ if TYPE_CHECKING:
|
|
18
18
|
from openpyxl.utils.dataframe import dataframe_to_rows
|
19
19
|
from openpyxl.workbook.workbook import Workbook
|
20
20
|
from openpyxl.worksheet.worksheet import Worksheet
|
21
|
-
from pandas import
|
21
|
+
from pandas import (
|
22
|
+
DataFrame,
|
23
|
+
DatetimeIndex,
|
24
|
+
Index,
|
25
|
+
MultiIndex,
|
26
|
+
Series,
|
27
|
+
date_range,
|
28
|
+
to_datetime,
|
29
|
+
)
|
22
30
|
from pandas.tseries.offsets import CustomBusinessDay
|
23
31
|
from plotly.graph_objs import Figure # type: ignore[import-untyped,unused-ignore]
|
24
32
|
from plotly.io import to_html # type: ignore[import-untyped,unused-ignore]
|
@@ -351,9 +359,7 @@ class _CommonModel(BaseModel):
|
|
351
359
|
"""
|
352
360
|
wmdf = self.tsdf.copy()
|
353
361
|
wmdf.index = DatetimeIndex(wmdf.index)
|
354
|
-
result = (
|
355
|
-
wmdf.resample("BME").last().pct_change(fill_method=cast(str, None)).min()
|
356
|
-
)
|
362
|
+
result = wmdf.resample("BME").last().pct_change().min()
|
357
363
|
|
358
364
|
if self.tsdf.shape[1] == 1:
|
359
365
|
return float(result.iloc[0])
|
@@ -521,8 +527,8 @@ class _CommonModel(BaseModel):
|
|
521
527
|
An OpenFrame object
|
522
528
|
|
523
529
|
"""
|
524
|
-
startyear =
|
525
|
-
endyear =
|
530
|
+
startyear = to_datetime(self.tsdf.index[0]).year
|
531
|
+
endyear = to_datetime(self.tsdf.index[-1]).year
|
526
532
|
calendar = holiday_calendar(
|
527
533
|
startyear=startyear,
|
528
534
|
endyear=endyear,
|
@@ -1021,9 +1027,7 @@ class _CommonModel(BaseModel):
|
|
1021
1027
|
time_factor = how_many / fraction
|
1022
1028
|
|
1023
1029
|
result = (
|
1024
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1025
|
-
.pct_change(fill_method=cast(str, None))
|
1026
|
-
.mean()
|
1030
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change().mean()
|
1027
1031
|
* time_factor
|
1028
1032
|
)
|
1029
1033
|
|
@@ -1081,9 +1085,7 @@ class _CommonModel(BaseModel):
|
|
1081
1085
|
time_factor = how_many / fraction
|
1082
1086
|
|
1083
1087
|
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1084
|
-
result = (
|
1085
|
-
data.pct_change(fill_method=cast(str, None)).std().mul(sqrt(time_factor))
|
1086
|
-
)
|
1088
|
+
result = data.pct_change().std().mul(sqrt(time_factor))
|
1087
1089
|
|
1088
1090
|
if self.tsdf.shape[1] == 1:
|
1089
1091
|
return float(cast(SupportsFloat, result.iloc[0]))
|
@@ -1279,22 +1281,20 @@ class _CommonModel(BaseModel):
|
|
1279
1281
|
if drift_adjust:
|
1280
1282
|
imp_vol = (-sqrt(time_factor) / norm.ppf(level)) * (
|
1281
1283
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1282
|
-
.pct_change(
|
1284
|
+
.pct_change()
|
1283
1285
|
.quantile(1 - level, interpolation=interpolation)
|
1284
1286
|
- self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1285
|
-
.pct_change(
|
1287
|
+
.pct_change()
|
1286
1288
|
.sum()
|
1287
1289
|
/ len(
|
1288
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
1289
|
-
fill_method=cast(str, None),
|
1290
|
-
),
|
1290
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
|
1291
1291
|
)
|
1292
1292
|
)
|
1293
1293
|
else:
|
1294
1294
|
imp_vol = (
|
1295
1295
|
-sqrt(time_factor)
|
1296
1296
|
* self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1297
|
-
.pct_change(
|
1297
|
+
.pct_change()
|
1298
1298
|
.quantile(1 - level, interpolation=interpolation)
|
1299
1299
|
/ norm.ppf(level)
|
1300
1300
|
)
|
@@ -1357,14 +1357,14 @@ class _CommonModel(BaseModel):
|
|
1357
1357
|
cvar_df = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy(deep=True)
|
1358
1358
|
result = [
|
1359
1359
|
cvar_df.loc[:, x] # type: ignore[call-overload,index]
|
1360
|
-
.pct_change(
|
1360
|
+
.pct_change()
|
1361
1361
|
.sort_values()
|
1362
1362
|
.iloc[
|
1363
1363
|
: int(
|
1364
1364
|
ceil(
|
1365
1365
|
(1 - level)
|
1366
1366
|
* cvar_df.loc[:, x] # type: ignore[index]
|
1367
|
-
.pct_change(
|
1367
|
+
.pct_change()
|
1368
1368
|
.count(),
|
1369
1369
|
),
|
1370
1370
|
)
|
@@ -1424,7 +1424,7 @@ class _CommonModel(BaseModel):
|
|
1424
1424
|
)
|
1425
1425
|
how_many = (
|
1426
1426
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1427
|
-
.pct_change(
|
1427
|
+
.pct_change()
|
1428
1428
|
.count(numeric_only=True)
|
1429
1429
|
)
|
1430
1430
|
if periods_in_a_year_fixed:
|
@@ -1439,7 +1439,7 @@ class _CommonModel(BaseModel):
|
|
1439
1439
|
|
1440
1440
|
dddf = (
|
1441
1441
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1442
|
-
.pct_change(
|
1442
|
+
.pct_change()
|
1443
1443
|
.sub(min_accepted_return / time_factor)
|
1444
1444
|
)
|
1445
1445
|
|
@@ -1542,7 +1542,7 @@ class _CommonModel(BaseModel):
|
|
1542
1542
|
)
|
1543
1543
|
result: NDArray[float64] = skew(
|
1544
1544
|
a=self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1545
|
-
.pct_change(
|
1545
|
+
.pct_change()
|
1546
1546
|
.to_numpy(),
|
1547
1547
|
bias=True,
|
1548
1548
|
nan_policy="omit",
|
@@ -1589,9 +1589,7 @@ class _CommonModel(BaseModel):
|
|
1589
1589
|
to_dt=to_date,
|
1590
1590
|
)
|
1591
1591
|
result: NDArray[float64] = kurtosis(
|
1592
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
1593
|
-
fill_method=cast(str, None),
|
1594
|
-
),
|
1592
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
|
1595
1593
|
fisher=True,
|
1596
1594
|
bias=True,
|
1597
1595
|
nan_policy="omit",
|
@@ -1687,19 +1685,13 @@ class _CommonModel(BaseModel):
|
|
1687
1685
|
)
|
1688
1686
|
pos = (
|
1689
1687
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1690
|
-
.pct_change(
|
1691
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
1692
|
-
fill_method=cast(str, None),
|
1693
|
-
)[1:]
|
1688
|
+
.pct_change()[1:][
|
1689
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()[1:]
|
1694
1690
|
> zero
|
1695
1691
|
]
|
1696
1692
|
.count()
|
1697
1693
|
)
|
1698
|
-
tot = (
|
1699
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1700
|
-
.pct_change(fill_method=cast(str, None))[1:]
|
1701
|
-
.count()
|
1702
|
-
)
|
1694
|
+
tot = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change().count()
|
1703
1695
|
share = pos / tot
|
1704
1696
|
if self.tsdf.shape[1] == 1:
|
1705
1697
|
return float(share.iloc[0])
|
@@ -1875,9 +1867,7 @@ class _CommonModel(BaseModel):
|
|
1875
1867
|
from_dt=from_date,
|
1876
1868
|
to_dt=to_date,
|
1877
1869
|
)
|
1878
|
-
retdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
1879
|
-
fill_method=cast(str, None),
|
1880
|
-
)
|
1870
|
+
retdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
|
1881
1871
|
pos = retdf[retdf > min_accepted_return].sub(min_accepted_return).sum()
|
1882
1872
|
neg = retdf[retdf < min_accepted_return].sub(min_accepted_return).sum()
|
1883
1873
|
ratio = pos / -neg
|
@@ -1965,7 +1955,7 @@ class _CommonModel(BaseModel):
|
|
1965
1955
|
period = "-".join([str(year), str(month).zfill(2)])
|
1966
1956
|
vrdf = self.tsdf.copy()
|
1967
1957
|
vrdf.index = DatetimeIndex(vrdf.index)
|
1968
|
-
resultdf = DataFrame(vrdf.pct_change(
|
1958
|
+
resultdf = DataFrame(vrdf.pct_change())
|
1969
1959
|
result = resultdf.loc[period] + 1
|
1970
1960
|
cal_period = result.cumprod(axis="index").iloc[-1] - 1
|
1971
1961
|
if self.tsdf.shape[1] == 1:
|
@@ -2017,7 +2007,7 @@ class _CommonModel(BaseModel):
|
|
2017
2007
|
)
|
2018
2008
|
result = (
|
2019
2009
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
2020
|
-
.pct_change(
|
2010
|
+
.pct_change()
|
2021
2011
|
.quantile(1 - level, interpolation=interpolation)
|
2022
2012
|
)
|
2023
2013
|
|
@@ -2065,7 +2055,7 @@ class _CommonModel(BaseModel):
|
|
2065
2055
|
)
|
2066
2056
|
result = (
|
2067
2057
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
2068
|
-
.pct_change(
|
2058
|
+
.pct_change()
|
2069
2059
|
.rolling(observations, min_periods=observations)
|
2070
2060
|
.sum()
|
2071
2061
|
.min()
|
@@ -2111,9 +2101,7 @@ class _CommonModel(BaseModel):
|
|
2111
2101
|
from_dt=from_date,
|
2112
2102
|
to_dt=to_date,
|
2113
2103
|
)
|
2114
|
-
zscframe = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
2115
|
-
fill_method=cast(str, None),
|
2116
|
-
)
|
2104
|
+
zscframe = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
|
2117
2105
|
result = (zscframe.iloc[-1] - zscframe.mean()) / zscframe.std()
|
2118
2106
|
|
2119
2107
|
if self.tsdf.shape[1] == 1:
|
@@ -2182,7 +2170,7 @@ class _CommonModel(BaseModel):
|
|
2182
2170
|
ret_label = cast(tuple[str], self.tsdf.iloc[:, column].name)[0]
|
2183
2171
|
retseries = (
|
2184
2172
|
self.tsdf.iloc[:, column]
|
2185
|
-
.pct_change(
|
2173
|
+
.pct_change()
|
2186
2174
|
.rolling(observations, min_periods=observations)
|
2187
2175
|
.sum()
|
2188
2176
|
)
|
@@ -2259,7 +2247,7 @@ class _CommonModel(BaseModel):
|
|
2259
2247
|
else:
|
2260
2248
|
time_factor = self.periods_in_a_year
|
2261
2249
|
vol_label = cast(tuple[str, ValueType], self.tsdf.iloc[:, column].name)[0]
|
2262
|
-
dframe = self.tsdf.iloc[:, column].pct_change(
|
2250
|
+
dframe = self.tsdf.iloc[:, column].pct_change()
|
2263
2251
|
volseries = dframe.rolling(
|
2264
2252
|
observations,
|
2265
2253
|
min_periods=observations,
|
openseries/datefixer.py
CHANGED
@@ -336,7 +336,7 @@ def generate_calendar_date_range(
|
|
336
336
|
)
|
337
337
|
calendar = holiday_calendar(
|
338
338
|
startyear=start.year,
|
339
|
-
endyear=tmp_range[-1].year,
|
339
|
+
endyear=date_fix(tmp_range[-1]).year,
|
340
340
|
countries=countries,
|
341
341
|
)
|
342
342
|
return [
|
@@ -351,7 +351,7 @@ def generate_calendar_date_range(
|
|
351
351
|
if end and not start:
|
352
352
|
tmp_range = date_range(end=end, periods=trading_days * 365 // 252, freq="D")
|
353
353
|
calendar = holiday_calendar(
|
354
|
-
startyear=tmp_range[0].year,
|
354
|
+
startyear=date_fix(tmp_range[0]).year,
|
355
355
|
endyear=end.year,
|
356
356
|
countries=countries,
|
357
357
|
)
|
@@ -371,7 +371,6 @@ def generate_calendar_date_range(
|
|
371
371
|
raise ValueError(msg)
|
372
372
|
|
373
373
|
|
374
|
-
# noinspection PyUnusedLocal
|
375
374
|
def _do_resample_to_business_period_ends(
|
376
375
|
data: DataFrame,
|
377
376
|
freq: LiteralBizDayFreq,
|
@@ -421,4 +420,4 @@ def _do_resample_to_business_period_ends(
|
|
421
420
|
]
|
422
421
|
+ [copydata.index[-1]],
|
423
422
|
)
|
424
|
-
return dates.drop_duplicates()
|
423
|
+
return DatetimeIndex(dates.drop_duplicates())
|
openseries/frame.py
CHANGED
@@ -336,7 +336,7 @@ class OpenFrame(_CommonModel):
|
|
336
336
|
The returns of the values in the series
|
337
337
|
|
338
338
|
"""
|
339
|
-
self.tsdf = self.tsdf.pct_change(
|
339
|
+
self.tsdf = self.tsdf.pct_change()
|
340
340
|
self.tsdf.iloc[0] = 0
|
341
341
|
new_labels = [ValueType.RTRN] * self.item_count
|
342
342
|
arrays = [self.tsdf.columns.get_level_values(0), new_labels]
|
@@ -449,7 +449,8 @@ class OpenFrame(_CommonModel):
|
|
449
449
|
countries=countries,
|
450
450
|
)
|
451
451
|
xerie.tsdf = xerie.tsdf.reindex(
|
452
|
-
[deyt.date() for deyt in dates],
|
452
|
+
[deyt.date() for deyt in dates],
|
453
|
+
method=method,
|
453
454
|
)
|
454
455
|
|
455
456
|
self._set_tsdf()
|
@@ -588,7 +589,7 @@ class OpenFrame(_CommonModel):
|
|
588
589
|
Correlation matrix
|
589
590
|
|
590
591
|
"""
|
591
|
-
corr_matrix = self.tsdf.pct_change(
|
592
|
+
corr_matrix = self.tsdf.pct_change().corr(
|
592
593
|
method="pearson",
|
593
594
|
min_periods=1,
|
594
595
|
)
|
@@ -615,7 +616,6 @@ class OpenFrame(_CommonModel):
|
|
615
616
|
|
616
617
|
"""
|
617
618
|
self.constituents += [new_series]
|
618
|
-
# noinspection PyUnreachableCode
|
619
619
|
self.tsdf = concat([self.tsdf, new_series.tsdf], axis="columns", sort=True)
|
620
620
|
return self
|
621
621
|
|
@@ -814,10 +814,9 @@ class OpenFrame(_CommonModel):
|
|
814
814
|
:,
|
815
815
|
item,
|
816
816
|
]
|
817
|
-
# noinspection PyTypeChecker
|
818
817
|
relative = 1.0 + longdf - shortdf
|
819
818
|
vol = float(
|
820
|
-
relative.pct_change(
|
819
|
+
relative.pct_change().std() * sqrt(time_factor),
|
821
820
|
)
|
822
821
|
terrors.append(vol)
|
823
822
|
|
@@ -905,13 +904,12 @@ class OpenFrame(_CommonModel):
|
|
905
904
|
:,
|
906
905
|
item,
|
907
906
|
]
|
908
|
-
# noinspection PyTypeChecker
|
909
907
|
relative = 1.0 + longdf - shortdf
|
910
908
|
ret = float(
|
911
|
-
relative.pct_change(
|
909
|
+
relative.pct_change().mean() * time_factor,
|
912
910
|
)
|
913
911
|
vol = float(
|
914
|
-
relative.pct_change(
|
912
|
+
relative.pct_change().std() * sqrt(time_factor),
|
915
913
|
)
|
916
914
|
ratios.append(ret / vol)
|
917
915
|
|
@@ -1010,18 +1008,16 @@ class OpenFrame(_CommonModel):
|
|
1010
1008
|
msg = "ratio must be one of 'up', 'down' or 'both'."
|
1011
1009
|
if ratio == "up":
|
1012
1010
|
uparray = (
|
1013
|
-
longdf.pct_change(
|
1014
|
-
shortdf.pct_change(
|
1015
|
-
> loss_limit
|
1011
|
+
longdf.pct_change()[
|
1012
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1016
1013
|
]
|
1017
1014
|
.add(1)
|
1018
1015
|
.to_numpy()
|
1019
1016
|
)
|
1020
1017
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1021
1018
|
upidxarray = (
|
1022
|
-
shortdf.pct_change(
|
1023
|
-
shortdf.pct_change(
|
1024
|
-
> loss_limit
|
1019
|
+
shortdf.pct_change()[
|
1020
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1025
1021
|
]
|
1026
1022
|
.add(1)
|
1027
1023
|
.to_numpy()
|
@@ -1032,9 +1028,8 @@ class OpenFrame(_CommonModel):
|
|
1032
1028
|
ratios.append(up_rtrn / up_idx_return)
|
1033
1029
|
elif ratio == "down":
|
1034
1030
|
downarray = (
|
1035
|
-
longdf.pct_change(
|
1036
|
-
shortdf.pct_change(
|
1037
|
-
< loss_limit
|
1031
|
+
longdf.pct_change()[
|
1032
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1038
1033
|
]
|
1039
1034
|
.add(1)
|
1040
1035
|
.to_numpy()
|
@@ -1043,9 +1038,8 @@ class OpenFrame(_CommonModel):
|
|
1043
1038
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1044
1039
|
)
|
1045
1040
|
downidxarray = (
|
1046
|
-
shortdf.pct_change(
|
1047
|
-
shortdf.pct_change(
|
1048
|
-
< loss_limit
|
1041
|
+
shortdf.pct_change()[
|
1042
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1049
1043
|
]
|
1050
1044
|
.add(1)
|
1051
1045
|
.to_numpy()
|
@@ -1057,18 +1051,16 @@ class OpenFrame(_CommonModel):
|
|
1057
1051
|
ratios.append(down_return / down_idx_return)
|
1058
1052
|
elif ratio == "both":
|
1059
1053
|
uparray = (
|
1060
|
-
longdf.pct_change(
|
1061
|
-
shortdf.pct_change(
|
1062
|
-
> loss_limit
|
1054
|
+
longdf.pct_change()[
|
1055
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1063
1056
|
]
|
1064
1057
|
.add(1)
|
1065
1058
|
.to_numpy()
|
1066
1059
|
)
|
1067
1060
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1068
1061
|
upidxarray = (
|
1069
|
-
shortdf.pct_change(
|
1070
|
-
shortdf.pct_change(
|
1071
|
-
> loss_limit
|
1062
|
+
shortdf.pct_change()[
|
1063
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1072
1064
|
]
|
1073
1065
|
.add(1)
|
1074
1066
|
.to_numpy()
|
@@ -1077,9 +1069,8 @@ class OpenFrame(_CommonModel):
|
|
1077
1069
|
upidxarray.prod() ** (1 / (len(upidxarray) / time_factor)) - 1
|
1078
1070
|
)
|
1079
1071
|
downarray = (
|
1080
|
-
longdf.pct_change(
|
1081
|
-
shortdf.pct_change(
|
1082
|
-
< loss_limit
|
1072
|
+
longdf.pct_change()[
|
1073
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1083
1074
|
]
|
1084
1075
|
.add(1)
|
1085
1076
|
.to_numpy()
|
@@ -1088,9 +1079,8 @@ class OpenFrame(_CommonModel):
|
|
1088
1079
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1089
1080
|
)
|
1090
1081
|
downidxarray = (
|
1091
|
-
shortdf.pct_change(
|
1092
|
-
shortdf.pct_change(
|
1093
|
-
< loss_limit
|
1082
|
+
shortdf.pct_change()[
|
1083
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1094
1084
|
]
|
1095
1085
|
.add(1)
|
1096
1086
|
.to_numpy()
|
@@ -1422,7 +1412,7 @@ class OpenFrame(_CommonModel):
|
|
1422
1412
|
x == ValueType.RTRN
|
1423
1413
|
for x in self.tsdf.columns.get_level_values(1).to_numpy()
|
1424
1414
|
):
|
1425
|
-
dframe = dframe.pct_change(
|
1415
|
+
dframe = dframe.pct_change()
|
1426
1416
|
dframe.iloc[0] = 0
|
1427
1417
|
|
1428
1418
|
msg = "Weight strategy not implemented"
|
@@ -1492,13 +1482,11 @@ class OpenFrame(_CommonModel):
|
|
1492
1482
|
)
|
1493
1483
|
|
1494
1484
|
retseries = (
|
1495
|
-
relative.pct_change(
|
1496
|
-
.rolling(observations, min_periods=observations)
|
1497
|
-
.sum()
|
1485
|
+
relative.pct_change().rolling(observations, min_periods=observations).sum()
|
1498
1486
|
)
|
1499
1487
|
retdf = retseries.dropna().to_frame()
|
1500
1488
|
|
1501
|
-
voldf = relative.pct_change(
|
1489
|
+
voldf = relative.pct_change().rolling(
|
1502
1490
|
observations,
|
1503
1491
|
min_periods=observations,
|
1504
1492
|
).std() * sqrt(time_factor)
|
@@ -1542,7 +1530,7 @@ class OpenFrame(_CommonModel):
|
|
1542
1530
|
asset_label = cast(tuple[str, str], self.tsdf.iloc[:, asset_column].name)[0]
|
1543
1531
|
beta_label = f"{asset_label} / {market_label}"
|
1544
1532
|
|
1545
|
-
rolling = self.tsdf.pct_change(
|
1533
|
+
rolling = self.tsdf.pct_change().rolling(
|
1546
1534
|
observations,
|
1547
1535
|
min_periods=observations,
|
1548
1536
|
)
|
@@ -1599,12 +1587,10 @@ class OpenFrame(_CommonModel):
|
|
1599
1587
|
)
|
1600
1588
|
first_series = (
|
1601
1589
|
self.tsdf.iloc[:, first_column]
|
1602
|
-
.pct_change(
|
1590
|
+
.pct_change()[1:]
|
1603
1591
|
.rolling(observations, min_periods=observations)
|
1604
1592
|
)
|
1605
|
-
second_series = self.tsdf.iloc[:, second_column].pct_change(
|
1606
|
-
fill_method=None,
|
1607
|
-
)[1:]
|
1593
|
+
second_series = self.tsdf.iloc[:, second_column].pct_change()[1:]
|
1608
1594
|
corrdf = first_series.corr(other=second_series).dropna().to_frame()
|
1609
1595
|
corrdf.columns = MultiIndex.from_arrays(
|
1610
1596
|
[
|
openseries/portfoliotools.py
CHANGED
@@ -105,15 +105,14 @@ def simulate_portfolios(
|
|
105
105
|
weights = weights / npsum(weights)
|
106
106
|
all_weights[x, :] = weights
|
107
107
|
|
108
|
-
vol_arr[x] = sqrt(
|
109
|
-
|
108
|
+
vol_arr[x] = sqrt(
|
109
|
+
weights.T @ (log_ret.cov() * simframe.periods_in_a_year @ weights),
|
110
110
|
)
|
111
111
|
|
112
112
|
ret_arr[x] = npsum(log_ret.mean() * weights * simframe.periods_in_a_year)
|
113
113
|
|
114
114
|
sharpe_arr[x] = ret_arr[x] / vol_arr[x]
|
115
115
|
|
116
|
-
# noinspection PyUnreachableCode
|
117
116
|
simdf = concat(
|
118
117
|
[
|
119
118
|
DataFrame({"stdev": vol_arr, "ret": ret_arr, "sharpe": sharpe_arr}),
|
@@ -284,7 +283,6 @@ def efficient_frontier( # noqa: C901
|
|
284
283
|
frontier_x.append(result["fun"])
|
285
284
|
frontier_weights.append(result["x"])
|
286
285
|
|
287
|
-
# noinspection PyUnreachableCode
|
288
286
|
line_df = concat(
|
289
287
|
[
|
290
288
|
DataFrame(data=frontier_weights, columns=eframe.columns_lvl_zero),
|
openseries/series.py
CHANGED
@@ -205,7 +205,7 @@ class OpenTimeSeries(_CommonModel):
|
|
205
205
|
@classmethod
|
206
206
|
def from_df(
|
207
207
|
cls: type[OpenTimeSeries],
|
208
|
-
dframe:
|
208
|
+
dframe: Series[float] | DataFrame,
|
209
209
|
column_nmbr: int = 0,
|
210
210
|
valuetype: ValueType = ValueType.PRICE,
|
211
211
|
baseccy: CurrencyStringType = "SEK",
|
@@ -234,10 +234,8 @@ class OpenTimeSeries(_CommonModel):
|
|
234
234
|
|
235
235
|
"""
|
236
236
|
msg = "Argument dframe must be pandas Series or DataFrame."
|
237
|
-
if isinstance(dframe, Series):
|
238
|
-
if isinstance(
|
239
|
-
dframe.name, tuple,
|
240
|
-
):
|
237
|
+
if isinstance(dframe, Series):
|
238
|
+
if isinstance(dframe.name, tuple):
|
241
239
|
label, _ = dframe.name
|
242
240
|
else:
|
243
241
|
label = dframe.name
|
@@ -436,7 +434,7 @@ class OpenTimeSeries(_CommonModel):
|
|
436
434
|
The returns of the values in the series
|
437
435
|
|
438
436
|
"""
|
439
|
-
self.tsdf = self.tsdf.pct_change(
|
437
|
+
self.tsdf = self.tsdf.pct_change()
|
440
438
|
self.tsdf.iloc[0] = 0
|
441
439
|
self.valuetype = ValueType.RTRN
|
442
440
|
self.tsdf.columns = MultiIndex.from_arrays(
|
@@ -696,7 +694,7 @@ class OpenTimeSeries(_CommonModel):
|
|
696
694
|
returns_input = True
|
697
695
|
else:
|
698
696
|
values = [cast(float, self.tsdf.iloc[0, 0])]
|
699
|
-
ra_df = self.tsdf.pct_change(
|
697
|
+
ra_df = self.tsdf.pct_change()
|
700
698
|
returns_input = False
|
701
699
|
ra_df = ra_df.dropna()
|
702
700
|
|
openseries/simulation.py
CHANGED
@@ -122,10 +122,7 @@ class ReturnSimulation(BaseModel):
|
|
122
122
|
"""
|
123
123
|
return cast(
|
124
124
|
float,
|
125
|
-
(
|
126
|
-
self.results.pct_change(fill_method=cast(str, None)).mean()
|
127
|
-
* self.trading_days_in_year
|
128
|
-
).iloc[0],
|
125
|
+
(self.results.pct_change().mean() * self.trading_days_in_year).iloc[0],
|
129
126
|
)
|
130
127
|
|
131
128
|
@property
|
@@ -140,10 +137,9 @@ class ReturnSimulation(BaseModel):
|
|
140
137
|
"""
|
141
138
|
return cast(
|
142
139
|
float,
|
143
|
-
(
|
144
|
-
|
145
|
-
|
146
|
-
).iloc[0],
|
140
|
+
(self.results.pct_change().std() * sqrt(self.trading_days_in_year)).iloc[
|
141
|
+
0
|
142
|
+
],
|
147
143
|
)
|
148
144
|
|
149
145
|
@classmethod
|
openseries/types.py
CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import datetime as dt
|
6
6
|
from enum import Enum
|
7
|
+
from pprint import pformat
|
7
8
|
from typing import Annotated, ClassVar, Literal, Union
|
8
9
|
|
9
10
|
from numpy import datetime64
|
@@ -225,8 +226,8 @@ LiteralFrameProps = Literal[
|
|
225
226
|
]
|
226
227
|
|
227
228
|
|
228
|
-
class
|
229
|
-
"""
|
229
|
+
class PropertiesList(list[str]):
|
230
|
+
"""Base class for allowed property arguments definition."""
|
230
231
|
|
231
232
|
allowed_strings: ClassVar[set[str]] = {
|
232
233
|
"value_ret",
|
@@ -246,9 +247,38 @@ class OpenTimeSeriesPropertiesList(list[str]):
|
|
246
247
|
"vol_from_var",
|
247
248
|
"worst",
|
248
249
|
"worst_month",
|
249
|
-
"max_drawdown_cal_year",
|
250
250
|
"max_drawdown",
|
251
251
|
"max_drawdown_date",
|
252
|
+
"max_drawdown_cal_year",
|
253
|
+
}
|
254
|
+
|
255
|
+
def _validate(self: Self) -> None:
|
256
|
+
"""Validate the string input of the all_properties method."""
|
257
|
+
seen = set()
|
258
|
+
invalids = set()
|
259
|
+
duplicates = set()
|
260
|
+
msg = ""
|
261
|
+
for item in self:
|
262
|
+
if item not in self.allowed_strings:
|
263
|
+
invalids.add(item)
|
264
|
+
if item in seen:
|
265
|
+
duplicates.add(item)
|
266
|
+
seen.add(item)
|
267
|
+
if len(invalids) != 0:
|
268
|
+
msg += (
|
269
|
+
f"Invalid string(s): {list(invalids)}.\nAllowed strings are:"
|
270
|
+
f"\n{pformat(self.allowed_strings)}\n"
|
271
|
+
)
|
272
|
+
if len(duplicates) != 0:
|
273
|
+
msg += f"Duplicate string(s): {list(duplicates)}."
|
274
|
+
if len(msg) != 0:
|
275
|
+
raise ValueError(msg)
|
276
|
+
|
277
|
+
|
278
|
+
class OpenTimeSeriesPropertiesList(PropertiesList):
|
279
|
+
"""Allowed property arguments for the OpenTimeSeries class."""
|
280
|
+
|
281
|
+
allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | {
|
252
282
|
"first_idx",
|
253
283
|
"last_idx",
|
254
284
|
"length",
|
@@ -265,44 +295,11 @@ class OpenTimeSeriesPropertiesList(list[str]):
|
|
265
295
|
super().__init__(args)
|
266
296
|
self._validate()
|
267
297
|
|
268
|
-
def _validate(self: Self) -> None:
|
269
|
-
seen = set()
|
270
|
-
for item in self:
|
271
|
-
if item not in self.allowed_strings:
|
272
|
-
msg = (
|
273
|
-
f"Invalid string: {item}. Allowed strings: {self.allowed_strings}"
|
274
|
-
)
|
275
|
-
raise ValueError(msg)
|
276
|
-
if item in seen:
|
277
|
-
msg = f"Duplicate string: {item}"
|
278
|
-
raise ValueError(msg)
|
279
|
-
seen.add(item)
|
280
|
-
|
281
298
|
|
282
|
-
class OpenFramePropertiesList(
|
299
|
+
class OpenFramePropertiesList(PropertiesList):
|
283
300
|
"""Allowed property arguments for the OpenFrame class."""
|
284
301
|
|
285
|
-
allowed_strings: ClassVar[set[str]] = {
|
286
|
-
"value_ret",
|
287
|
-
"geo_ret",
|
288
|
-
"arithmetic_ret",
|
289
|
-
"vol",
|
290
|
-
"downside_deviation",
|
291
|
-
"ret_vol_ratio",
|
292
|
-
"sortino_ratio",
|
293
|
-
"omega_ratio",
|
294
|
-
"z_score",
|
295
|
-
"skew",
|
296
|
-
"kurtosis",
|
297
|
-
"positive_share",
|
298
|
-
"var_down",
|
299
|
-
"cvar_down",
|
300
|
-
"vol_from_var",
|
301
|
-
"worst",
|
302
|
-
"worst_month",
|
303
|
-
"max_drawdown",
|
304
|
-
"max_drawdown_date",
|
305
|
-
"max_drawdown_cal_year",
|
302
|
+
allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | {
|
306
303
|
"first_indices",
|
307
304
|
"last_indices",
|
308
305
|
"lengths_of_items",
|
@@ -314,19 +311,6 @@ class OpenFramePropertiesList(list[str]):
|
|
314
311
|
super().__init__(args)
|
315
312
|
self._validate()
|
316
313
|
|
317
|
-
def _validate(self: Self) -> None:
|
318
|
-
seen = set()
|
319
|
-
for item in self:
|
320
|
-
if item not in self.allowed_strings:
|
321
|
-
msg = (
|
322
|
-
f"Invalid string: {item}. Allowed strings: {self.allowed_strings}"
|
323
|
-
)
|
324
|
-
raise ValueError(msg)
|
325
|
-
if item in seen:
|
326
|
-
msg = f"Duplicate string: {item}"
|
327
|
-
raise ValueError(msg)
|
328
|
-
seen.add(item)
|
329
|
-
|
330
314
|
|
331
315
|
class ValueType(str, Enum):
|
332
316
|
"""Enum types of OpenTimeSeries to identify the output."""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: openseries
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.3
|
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,<2.0.0)
|
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
|
@@ -0,0 +1,16 @@
|
|
1
|
+
openseries/__init__.py,sha256=gD2dMKRTJ9HMXLca_5sR67xGiU5sWExwaNUi-9N_RGQ,1032
|
2
|
+
openseries/_common_model.py,sha256=t3UeFpNIgWeeGW7lcsVAPF0lsdzNoBDvh__B1TMzigk,72772
|
3
|
+
openseries/_risk.py,sha256=PReIfkzhInvIgJkzI4k1wYvhmLZ4cCurYKuQAvlHLlE,2082
|
4
|
+
openseries/datefixer.py,sha256=3E8Ddf3Ps9k5qUWqL13ONelyMECZsvtyrsFJg7r3bxE,12298
|
5
|
+
openseries/frame.py,sha256=xXBPgKGpuHE2M3P4aHitWiJaeWXE3dbd5oEvxrg_7wY,54065
|
6
|
+
openseries/load_plotly.py,sha256=Uuk_-iIY4_C6Z5U3rAneOh8ZlGYWbkuis9s4Amxzko4,1921
|
7
|
+
openseries/plotly_captor_logo.json,sha256=F5nhMzEyxKywtjvQqMTKgKRCJQYMDIiBgDSxdte8Clo,178
|
8
|
+
openseries/plotly_layouts.json,sha256=ahx8-dL4_RPzvHtBOX0SiL0AH7xQJzNRSDhGrSmU-Og,1429
|
9
|
+
openseries/portfoliotools.py,sha256=pI-vUYKbht2u3dTpQ0MHiME67fP27HcaOCxuayW7v-0,18856
|
10
|
+
openseries/series.py,sha256=IvwyXu9hpsItw6LVPQAJ9YNe_bYCXO9ZyNEleuPCLIo,27766
|
11
|
+
openseries/simulation.py,sha256=jghKf0d2deXK-rqn8BeJ_s3JQVLL9FuE_IW29Kf1_-k,13816
|
12
|
+
openseries/types.py,sha256=IbzW9iVuA3MEx3WtjyB6QYeSd3TkGjTOkFnnpSADKlY,7491
|
13
|
+
openseries-1.7.3.dist-info/LICENSE.md,sha256=IQ8_IMXgHxyv4M48G14fJsjcrkiSASdalASTXWCOsj4,1515
|
14
|
+
openseries-1.7.3.dist-info/METADATA,sha256=HmKHHb3SqFrzGIzkKk-l5LXj3PiXagx5CkWH829gbxo,43973
|
15
|
+
openseries-1.7.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
16
|
+
openseries-1.7.3.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
1
|
-
openseries/__init__.py,sha256=gD2dMKRTJ9HMXLca_5sR67xGiU5sWExwaNUi-9N_RGQ,1032
|
2
|
-
openseries/_common_model.py,sha256=QuwlYSGCaTr6q0pPiHg6ni63ZvA8I2HfbgqSj4HmBHY,73657
|
3
|
-
openseries/_risk.py,sha256=PReIfkzhInvIgJkzI4k1wYvhmLZ4cCurYKuQAvlHLlE,2082
|
4
|
-
openseries/datefixer.py,sha256=IJiR43fVAf7hLJk0-5-xzQXNv6vAYxoJgRHH_EM1a9c,12292
|
5
|
-
openseries/frame.py,sha256=WNih33lji-oHkHQMLqMQ3CWz-jmfS4eLQrcXE0wzbys,54885
|
6
|
-
openseries/load_plotly.py,sha256=Uuk_-iIY4_C6Z5U3rAneOh8ZlGYWbkuis9s4Amxzko4,1921
|
7
|
-
openseries/plotly_captor_logo.json,sha256=F5nhMzEyxKywtjvQqMTKgKRCJQYMDIiBgDSxdte8Clo,178
|
8
|
-
openseries/plotly_layouts.json,sha256=ahx8-dL4_RPzvHtBOX0SiL0AH7xQJzNRSDhGrSmU-Og,1429
|
9
|
-
openseries/portfoliotools.py,sha256=WQSf2OdrSojx-uOhLWQzz6IMnCOZjWjh-8yPJmqTZcA,18933
|
10
|
-
openseries/series.py,sha256=PaXIVp7dLM4VsxPOe4B8tqM1MMDtJtPSDFvhv1Yblhc,27971
|
11
|
-
openseries/simulation.py,sha256=P5nkX7o3O_3pOL22MAZTqL_i4TB2IUmPGCodegsAM04,13932
|
12
|
-
openseries/types.py,sha256=C_t7WLyc7BgKun728CpLXnJS1GisGdNnxYAMBNAcVXU,7830
|
13
|
-
openseries-1.7.2.dist-info/LICENSE.md,sha256=IQ8_IMXgHxyv4M48G14fJsjcrkiSASdalASTXWCOsj4,1515
|
14
|
-
openseries-1.7.2.dist-info/METADATA,sha256=58Cg-cbff8KR3P_vvGUMEFvJg3SEy271FXNhEbIQudI,43974
|
15
|
-
openseries-1.7.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
16
|
-
openseries-1.7.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|