openseries 1.7.2__py3-none-any.whl → 1.7.4__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 +37 -45
- openseries/datefixer.py +3 -4
- openseries/frame.py +88 -85
- openseries/portfoliotools.py +14 -12
- openseries/series.py +15 -23
- openseries/simulation.py +4 -8
- openseries/types.py +35 -51
- {openseries-1.7.2.dist-info → openseries-1.7.4.dist-info}/METADATA +2 -2
- openseries-1.7.4.dist-info/RECORD +16 -0
- openseries-1.7.2.dist-info/RECORD +0 -16
- {openseries-1.7.2.dist-info → openseries-1.7.4.dist-info}/LICENSE.md +0 -0
- {openseries-1.7.2.dist-info → openseries-1.7.4.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]
|
@@ -51,6 +59,7 @@ from .types import (
|
|
51
59
|
)
|
52
60
|
|
53
61
|
|
62
|
+
# noinspection PyTypeChecker
|
54
63
|
class _CommonModel(BaseModel):
|
55
64
|
"""Declare _CommonModel."""
|
56
65
|
|
@@ -351,9 +360,7 @@ class _CommonModel(BaseModel):
|
|
351
360
|
"""
|
352
361
|
wmdf = self.tsdf.copy()
|
353
362
|
wmdf.index = DatetimeIndex(wmdf.index)
|
354
|
-
result = (
|
355
|
-
wmdf.resample("BME").last().pct_change(fill_method=cast(str, None)).min()
|
356
|
-
)
|
363
|
+
result = wmdf.resample("BME").last().pct_change().min()
|
357
364
|
|
358
365
|
if self.tsdf.shape[1] == 1:
|
359
366
|
return float(result.iloc[0])
|
@@ -521,8 +528,8 @@ class _CommonModel(BaseModel):
|
|
521
528
|
An OpenFrame object
|
522
529
|
|
523
530
|
"""
|
524
|
-
startyear =
|
525
|
-
endyear =
|
531
|
+
startyear = to_datetime(self.tsdf.index[0]).year
|
532
|
+
endyear = to_datetime(self.tsdf.index[-1]).year
|
526
533
|
calendar = holiday_calendar(
|
527
534
|
startyear=startyear,
|
528
535
|
endyear=endyear,
|
@@ -674,7 +681,7 @@ class _CommonModel(BaseModel):
|
|
674
681
|
output.append(dict(itemdata))
|
675
682
|
|
676
683
|
with dirpath.joinpath(filename).open(mode="w", encoding="utf-8") as jsonfile:
|
677
|
-
dump(output, jsonfile, indent=2, sort_keys=False)
|
684
|
+
dump(obj=output, fp=jsonfile, indent=2, sort_keys=False)
|
678
685
|
|
679
686
|
return output
|
680
687
|
|
@@ -1022,7 +1029,8 @@ class _CommonModel(BaseModel):
|
|
1022
1029
|
|
1023
1030
|
result = (
|
1024
1031
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1025
|
-
.
|
1032
|
+
.ffill()
|
1033
|
+
.pct_change()
|
1026
1034
|
.mean()
|
1027
1035
|
* time_factor
|
1028
1036
|
)
|
@@ -1081,9 +1089,7 @@ class _CommonModel(BaseModel):
|
|
1081
1089
|
time_factor = how_many / fraction
|
1082
1090
|
|
1083
1091
|
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
|
-
)
|
1092
|
+
result = data.ffill().pct_change().std().mul(sqrt(time_factor))
|
1087
1093
|
|
1088
1094
|
if self.tsdf.shape[1] == 1:
|
1089
1095
|
return float(cast(SupportsFloat, result.iloc[0]))
|
@@ -1279,22 +1285,20 @@ class _CommonModel(BaseModel):
|
|
1279
1285
|
if drift_adjust:
|
1280
1286
|
imp_vol = (-sqrt(time_factor) / norm.ppf(level)) * (
|
1281
1287
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1282
|
-
.pct_change(
|
1288
|
+
.pct_change()
|
1283
1289
|
.quantile(1 - level, interpolation=interpolation)
|
1284
1290
|
- self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1285
|
-
.pct_change(
|
1291
|
+
.pct_change()
|
1286
1292
|
.sum()
|
1287
1293
|
/ len(
|
1288
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
1289
|
-
fill_method=cast(str, None),
|
1290
|
-
),
|
1294
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
|
1291
1295
|
)
|
1292
1296
|
)
|
1293
1297
|
else:
|
1294
1298
|
imp_vol = (
|
1295
1299
|
-sqrt(time_factor)
|
1296
1300
|
* self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1297
|
-
.pct_change(
|
1301
|
+
.pct_change()
|
1298
1302
|
.quantile(1 - level, interpolation=interpolation)
|
1299
1303
|
/ norm.ppf(level)
|
1300
1304
|
)
|
@@ -1357,14 +1361,14 @@ class _CommonModel(BaseModel):
|
|
1357
1361
|
cvar_df = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy(deep=True)
|
1358
1362
|
result = [
|
1359
1363
|
cvar_df.loc[:, x] # type: ignore[call-overload,index]
|
1360
|
-
.pct_change(
|
1364
|
+
.pct_change()
|
1361
1365
|
.sort_values()
|
1362
1366
|
.iloc[
|
1363
1367
|
: int(
|
1364
1368
|
ceil(
|
1365
1369
|
(1 - level)
|
1366
1370
|
* cvar_df.loc[:, x] # type: ignore[index]
|
1367
|
-
.pct_change(
|
1371
|
+
.pct_change()
|
1368
1372
|
.count(),
|
1369
1373
|
),
|
1370
1374
|
)
|
@@ -1424,7 +1428,7 @@ class _CommonModel(BaseModel):
|
|
1424
1428
|
)
|
1425
1429
|
how_many = (
|
1426
1430
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1427
|
-
.pct_change(
|
1431
|
+
.pct_change()
|
1428
1432
|
.count(numeric_only=True)
|
1429
1433
|
)
|
1430
1434
|
if periods_in_a_year_fixed:
|
@@ -1439,7 +1443,7 @@ class _CommonModel(BaseModel):
|
|
1439
1443
|
|
1440
1444
|
dddf = (
|
1441
1445
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1442
|
-
.pct_change(
|
1446
|
+
.pct_change()
|
1443
1447
|
.sub(min_accepted_return / time_factor)
|
1444
1448
|
)
|
1445
1449
|
|
@@ -1542,7 +1546,7 @@ class _CommonModel(BaseModel):
|
|
1542
1546
|
)
|
1543
1547
|
result: NDArray[float64] = skew(
|
1544
1548
|
a=self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1545
|
-
.pct_change(
|
1549
|
+
.pct_change()
|
1546
1550
|
.to_numpy(),
|
1547
1551
|
bias=True,
|
1548
1552
|
nan_policy="omit",
|
@@ -1589,9 +1593,7 @@ class _CommonModel(BaseModel):
|
|
1589
1593
|
to_dt=to_date,
|
1590
1594
|
)
|
1591
1595
|
result: NDArray[float64] = kurtosis(
|
1592
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
1593
|
-
fill_method=cast(str, None),
|
1594
|
-
),
|
1596
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
|
1595
1597
|
fisher=True,
|
1596
1598
|
bias=True,
|
1597
1599
|
nan_policy="omit",
|
@@ -1687,19 +1689,13 @@ class _CommonModel(BaseModel):
|
|
1687
1689
|
)
|
1688
1690
|
pos = (
|
1689
1691
|
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:]
|
1692
|
+
.pct_change()[1:][
|
1693
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()[1:]
|
1694
1694
|
> zero
|
1695
1695
|
]
|
1696
1696
|
.count()
|
1697
1697
|
)
|
1698
|
-
tot = (
|
1699
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1700
|
-
.pct_change(fill_method=cast(str, None))[1:]
|
1701
|
-
.count()
|
1702
|
-
)
|
1698
|
+
tot = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change().count()
|
1703
1699
|
share = pos / tot
|
1704
1700
|
if self.tsdf.shape[1] == 1:
|
1705
1701
|
return float(share.iloc[0])
|
@@ -1875,9 +1871,7 @@ class _CommonModel(BaseModel):
|
|
1875
1871
|
from_dt=from_date,
|
1876
1872
|
to_dt=to_date,
|
1877
1873
|
)
|
1878
|
-
retdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
1879
|
-
fill_method=cast(str, None),
|
1880
|
-
)
|
1874
|
+
retdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
|
1881
1875
|
pos = retdf[retdf > min_accepted_return].sub(min_accepted_return).sum()
|
1882
1876
|
neg = retdf[retdf < min_accepted_return].sub(min_accepted_return).sum()
|
1883
1877
|
ratio = pos / -neg
|
@@ -1965,7 +1959,7 @@ class _CommonModel(BaseModel):
|
|
1965
1959
|
period = "-".join([str(year), str(month).zfill(2)])
|
1966
1960
|
vrdf = self.tsdf.copy()
|
1967
1961
|
vrdf.index = DatetimeIndex(vrdf.index)
|
1968
|
-
resultdf = DataFrame(vrdf.pct_change(
|
1962
|
+
resultdf = DataFrame(vrdf.pct_change())
|
1969
1963
|
result = resultdf.loc[period] + 1
|
1970
1964
|
cal_period = result.cumprod(axis="index").iloc[-1] - 1
|
1971
1965
|
if self.tsdf.shape[1] == 1:
|
@@ -2017,7 +2011,7 @@ class _CommonModel(BaseModel):
|
|
2017
2011
|
)
|
2018
2012
|
result = (
|
2019
2013
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
2020
|
-
.pct_change(
|
2014
|
+
.pct_change()
|
2021
2015
|
.quantile(1 - level, interpolation=interpolation)
|
2022
2016
|
)
|
2023
2017
|
|
@@ -2065,7 +2059,7 @@ class _CommonModel(BaseModel):
|
|
2065
2059
|
)
|
2066
2060
|
result = (
|
2067
2061
|
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
2068
|
-
.pct_change(
|
2062
|
+
.pct_change()
|
2069
2063
|
.rolling(observations, min_periods=observations)
|
2070
2064
|
.sum()
|
2071
2065
|
.min()
|
@@ -2111,9 +2105,7 @@ class _CommonModel(BaseModel):
|
|
2111
2105
|
from_dt=from_date,
|
2112
2106
|
to_dt=to_date,
|
2113
2107
|
)
|
2114
|
-
zscframe = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
|
2115
|
-
fill_method=cast(str, None),
|
2116
|
-
)
|
2108
|
+
zscframe = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
|
2117
2109
|
result = (zscframe.iloc[-1] - zscframe.mean()) / zscframe.std()
|
2118
2110
|
|
2119
2111
|
if self.tsdf.shape[1] == 1:
|
@@ -2182,7 +2174,7 @@ class _CommonModel(BaseModel):
|
|
2182
2174
|
ret_label = cast(tuple[str], self.tsdf.iloc[:, column].name)[0]
|
2183
2175
|
retseries = (
|
2184
2176
|
self.tsdf.iloc[:, column]
|
2185
|
-
.pct_change(
|
2177
|
+
.pct_change()
|
2186
2178
|
.rolling(observations, min_periods=observations)
|
2187
2179
|
.sum()
|
2188
2180
|
)
|
@@ -2259,7 +2251,7 @@ class _CommonModel(BaseModel):
|
|
2259
2251
|
else:
|
2260
2252
|
time_factor = self.periods_in_a_year
|
2261
2253
|
vol_label = cast(tuple[str, ValueType], self.tsdf.iloc[:, column].name)[0]
|
2262
|
-
dframe = self.tsdf.iloc[:, column].pct_change(
|
2254
|
+
dframe = self.tsdf.iloc[:, column].pct_change()
|
2263
2255
|
volseries = dframe.rolling(
|
2264
2256
|
observations,
|
2265
2257
|
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
@@ -14,7 +14,6 @@ if TYPE_CHECKING:
|
|
14
14
|
import statsmodels.api as sm # type: ignore[import-untyped,unused-ignore]
|
15
15
|
from numpy import (
|
16
16
|
cov,
|
17
|
-
cumprod,
|
18
17
|
divide,
|
19
18
|
isinf,
|
20
19
|
log,
|
@@ -336,11 +335,12 @@ class OpenFrame(_CommonModel):
|
|
336
335
|
The returns of the values in the series
|
337
336
|
|
338
337
|
"""
|
339
|
-
|
340
|
-
|
338
|
+
returns = self.tsdf.ffill().pct_change()
|
339
|
+
returns.iloc[0] = 0
|
341
340
|
new_labels = [ValueType.RTRN] * self.item_count
|
342
341
|
arrays = [self.tsdf.columns.get_level_values(0), new_labels]
|
343
|
-
|
342
|
+
returns.columns = MultiIndex.from_arrays(arrays=arrays)
|
343
|
+
self.tsdf = returns.copy()
|
344
344
|
return self
|
345
345
|
|
346
346
|
def value_to_diff(self: Self, periods: int = 1) -> Self:
|
@@ -374,14 +374,20 @@ class OpenFrame(_CommonModel):
|
|
374
374
|
An OpenFrame object
|
375
375
|
|
376
376
|
"""
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
377
|
+
vtypes = [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)]
|
378
|
+
if not any(vtypes):
|
379
|
+
returns = self.tsdf.ffill().pct_change()
|
380
|
+
returns.iloc[0] = 0
|
381
|
+
elif all(vtypes):
|
382
|
+
returns = self.tsdf.copy()
|
383
|
+
returns.iloc[0] = 0
|
384
|
+
else:
|
385
|
+
msg = "Mix of series types will give inconsistent results"
|
386
|
+
raise ValueError(msg)
|
387
|
+
|
388
|
+
returns = returns.add(1.0)
|
389
|
+
self.tsdf = returns.cumprod(axis=0) / returns.iloc[0]
|
382
390
|
|
383
|
-
self.tsdf = self.tsdf.add(1.0)
|
384
|
-
self.tsdf = self.tsdf.apply(cumprod, axis="index") / self.tsdf.iloc[0]
|
385
391
|
new_labels = [ValueType.PRICE] * self.item_count
|
386
392
|
arrays = [self.tsdf.columns.get_level_values(0), new_labels]
|
387
393
|
self.tsdf.columns = MultiIndex.from_arrays(arrays)
|
@@ -449,11 +455,19 @@ class OpenFrame(_CommonModel):
|
|
449
455
|
countries=countries,
|
450
456
|
)
|
451
457
|
xerie.tsdf = xerie.tsdf.reindex(
|
452
|
-
[deyt.date() for deyt in dates],
|
458
|
+
[deyt.date() for deyt in dates],
|
459
|
+
method=method,
|
453
460
|
)
|
454
461
|
|
462
|
+
arrays = [
|
463
|
+
self.tsdf.columns.get_level_values(0),
|
464
|
+
self.tsdf.columns.get_level_values(1),
|
465
|
+
]
|
466
|
+
|
455
467
|
self._set_tsdf()
|
456
468
|
|
469
|
+
self.tsdf.columns = MultiIndex.from_arrays(arrays)
|
470
|
+
|
457
471
|
return self
|
458
472
|
|
459
473
|
def ewma_risk(
|
@@ -588,7 +602,7 @@ class OpenFrame(_CommonModel):
|
|
588
602
|
Correlation matrix
|
589
603
|
|
590
604
|
"""
|
591
|
-
corr_matrix = self.tsdf.pct_change(
|
605
|
+
corr_matrix = self.tsdf.pct_change().corr(
|
592
606
|
method="pearson",
|
593
607
|
min_periods=1,
|
594
608
|
)
|
@@ -615,7 +629,6 @@ class OpenFrame(_CommonModel):
|
|
615
629
|
|
616
630
|
"""
|
617
631
|
self.constituents += [new_series]
|
618
|
-
# noinspection PyUnreachableCode
|
619
632
|
self.tsdf = concat([self.tsdf, new_series.tsdf], axis="columns", sort=True)
|
620
633
|
return self
|
621
634
|
|
@@ -814,10 +827,9 @@ class OpenFrame(_CommonModel):
|
|
814
827
|
:,
|
815
828
|
item,
|
816
829
|
]
|
817
|
-
# noinspection PyTypeChecker
|
818
830
|
relative = 1.0 + longdf - shortdf
|
819
831
|
vol = float(
|
820
|
-
relative.pct_change(
|
832
|
+
relative.pct_change().std() * sqrt(time_factor),
|
821
833
|
)
|
822
834
|
terrors.append(vol)
|
823
835
|
|
@@ -905,13 +917,12 @@ class OpenFrame(_CommonModel):
|
|
905
917
|
:,
|
906
918
|
item,
|
907
919
|
]
|
908
|
-
# noinspection PyTypeChecker
|
909
920
|
relative = 1.0 + longdf - shortdf
|
910
921
|
ret = float(
|
911
|
-
relative.pct_change(
|
922
|
+
relative.pct_change().mean() * time_factor,
|
912
923
|
)
|
913
924
|
vol = float(
|
914
|
-
relative.pct_change(
|
925
|
+
relative.pct_change().std() * sqrt(time_factor),
|
915
926
|
)
|
916
927
|
ratios.append(ret / vol)
|
917
928
|
|
@@ -1010,18 +1021,16 @@ class OpenFrame(_CommonModel):
|
|
1010
1021
|
msg = "ratio must be one of 'up', 'down' or 'both'."
|
1011
1022
|
if ratio == "up":
|
1012
1023
|
uparray = (
|
1013
|
-
longdf.pct_change(
|
1014
|
-
shortdf.pct_change(
|
1015
|
-
> loss_limit
|
1024
|
+
longdf.pct_change()[
|
1025
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1016
1026
|
]
|
1017
1027
|
.add(1)
|
1018
1028
|
.to_numpy()
|
1019
1029
|
)
|
1020
1030
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1021
1031
|
upidxarray = (
|
1022
|
-
shortdf.pct_change(
|
1023
|
-
shortdf.pct_change(
|
1024
|
-
> loss_limit
|
1032
|
+
shortdf.pct_change()[
|
1033
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1025
1034
|
]
|
1026
1035
|
.add(1)
|
1027
1036
|
.to_numpy()
|
@@ -1032,9 +1041,8 @@ class OpenFrame(_CommonModel):
|
|
1032
1041
|
ratios.append(up_rtrn / up_idx_return)
|
1033
1042
|
elif ratio == "down":
|
1034
1043
|
downarray = (
|
1035
|
-
longdf.pct_change(
|
1036
|
-
shortdf.pct_change(
|
1037
|
-
< loss_limit
|
1044
|
+
longdf.pct_change()[
|
1045
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1038
1046
|
]
|
1039
1047
|
.add(1)
|
1040
1048
|
.to_numpy()
|
@@ -1043,9 +1051,8 @@ class OpenFrame(_CommonModel):
|
|
1043
1051
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1044
1052
|
)
|
1045
1053
|
downidxarray = (
|
1046
|
-
shortdf.pct_change(
|
1047
|
-
shortdf.pct_change(
|
1048
|
-
< loss_limit
|
1054
|
+
shortdf.pct_change()[
|
1055
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1049
1056
|
]
|
1050
1057
|
.add(1)
|
1051
1058
|
.to_numpy()
|
@@ -1057,18 +1064,16 @@ class OpenFrame(_CommonModel):
|
|
1057
1064
|
ratios.append(down_return / down_idx_return)
|
1058
1065
|
elif ratio == "both":
|
1059
1066
|
uparray = (
|
1060
|
-
longdf.pct_change(
|
1061
|
-
shortdf.pct_change(
|
1062
|
-
> loss_limit
|
1067
|
+
longdf.pct_change()[
|
1068
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1063
1069
|
]
|
1064
1070
|
.add(1)
|
1065
1071
|
.to_numpy()
|
1066
1072
|
)
|
1067
1073
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1068
1074
|
upidxarray = (
|
1069
|
-
shortdf.pct_change(
|
1070
|
-
shortdf.pct_change(
|
1071
|
-
> loss_limit
|
1075
|
+
shortdf.pct_change()[
|
1076
|
+
shortdf.pct_change().to_numpy() > loss_limit
|
1072
1077
|
]
|
1073
1078
|
.add(1)
|
1074
1079
|
.to_numpy()
|
@@ -1077,9 +1082,8 @@ class OpenFrame(_CommonModel):
|
|
1077
1082
|
upidxarray.prod() ** (1 / (len(upidxarray) / time_factor)) - 1
|
1078
1083
|
)
|
1079
1084
|
downarray = (
|
1080
|
-
longdf.pct_change(
|
1081
|
-
shortdf.pct_change(
|
1082
|
-
< loss_limit
|
1085
|
+
longdf.pct_change()[
|
1086
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1083
1087
|
]
|
1084
1088
|
.add(1)
|
1085
1089
|
.to_numpy()
|
@@ -1088,9 +1092,8 @@ class OpenFrame(_CommonModel):
|
|
1088
1092
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1089
1093
|
)
|
1090
1094
|
downidxarray = (
|
1091
|
-
shortdf.pct_change(
|
1092
|
-
shortdf.pct_change(
|
1093
|
-
< loss_limit
|
1095
|
+
shortdf.pct_change()[
|
1096
|
+
shortdf.pct_change().to_numpy() < loss_limit
|
1094
1097
|
]
|
1095
1098
|
.add(1)
|
1096
1099
|
.to_numpy()
|
@@ -1294,30 +1297,8 @@ class OpenFrame(_CommonModel):
|
|
1294
1297
|
|
1295
1298
|
"""
|
1296
1299
|
full_year = 1.0
|
1297
|
-
|
1298
|
-
|
1299
|
-
for x in self.tsdf.columns.get_level_values(1).to_numpy()
|
1300
|
-
):
|
1301
|
-
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1302
|
-
if isinstance(asset, tuple):
|
1303
|
-
asset_log = self.tsdf.loc[:, asset]
|
1304
|
-
asset_cagr = asset_log.mean()
|
1305
|
-
elif isinstance(asset, int):
|
1306
|
-
asset_log = self.tsdf.iloc[:, asset]
|
1307
|
-
asset_cagr = asset_log.mean()
|
1308
|
-
else:
|
1309
|
-
raise TypeError(msg)
|
1310
|
-
|
1311
|
-
msg = "market should be a tuple[str, ValueType] or an integer."
|
1312
|
-
if isinstance(market, tuple):
|
1313
|
-
market_log = self.tsdf.loc[:, market]
|
1314
|
-
market_cagr = market_log.mean()
|
1315
|
-
elif isinstance(market, int):
|
1316
|
-
market_log = self.tsdf.iloc[:, market]
|
1317
|
-
market_cagr = market_log.mean()
|
1318
|
-
else:
|
1319
|
-
raise TypeError(msg)
|
1320
|
-
else:
|
1300
|
+
vtypes = [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)]
|
1301
|
+
if not any(vtypes):
|
1321
1302
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1322
1303
|
if isinstance(asset, tuple):
|
1323
1304
|
asset_log = log(
|
@@ -1385,6 +1366,29 @@ class OpenFrame(_CommonModel):
|
|
1385
1366
|
)
|
1386
1367
|
else:
|
1387
1368
|
raise TypeError(msg)
|
1369
|
+
elif all(vtypes):
|
1370
|
+
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1371
|
+
if isinstance(asset, tuple):
|
1372
|
+
asset_log = self.tsdf.loc[:, asset]
|
1373
|
+
asset_cagr = asset_log.mean()
|
1374
|
+
elif isinstance(asset, int):
|
1375
|
+
asset_log = self.tsdf.iloc[:, asset]
|
1376
|
+
asset_cagr = asset_log.mean()
|
1377
|
+
else:
|
1378
|
+
raise TypeError(msg)
|
1379
|
+
|
1380
|
+
msg = "market should be a tuple[str, ValueType] or an integer."
|
1381
|
+
if isinstance(market, tuple):
|
1382
|
+
market_log = self.tsdf.loc[:, market]
|
1383
|
+
market_cagr = market_log.mean()
|
1384
|
+
elif isinstance(market, int):
|
1385
|
+
market_log = self.tsdf.iloc[:, market]
|
1386
|
+
market_cagr = market_log.mean()
|
1387
|
+
else:
|
1388
|
+
raise TypeError(msg)
|
1389
|
+
else:
|
1390
|
+
msg = "Mix of series types will give inconsistent results"
|
1391
|
+
raise ValueError(msg)
|
1388
1392
|
|
1389
1393
|
covariance = cov(asset_log, market_log, ddof=dlta_degr_freedms)
|
1390
1394
|
beta = covariance[0, 1] / covariance[1, 1]
|
@@ -1417,27 +1421,30 @@ class OpenFrame(_CommonModel):
|
|
1417
1421
|
"to run the make_portfolio method."
|
1418
1422
|
)
|
1419
1423
|
raise ValueError(msg)
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1424
|
+
|
1425
|
+
vtypes = [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)]
|
1426
|
+
if not any(vtypes):
|
1427
|
+
returns = self.tsdf.ffill().pct_change()
|
1428
|
+
returns.iloc[0] = 0
|
1429
|
+
elif all(vtypes):
|
1430
|
+
returns = self.tsdf.copy()
|
1431
|
+
else:
|
1432
|
+
msg = "Mix of series types will give inconsistent results"
|
1433
|
+
raise ValueError(msg)
|
1427
1434
|
|
1428
1435
|
msg = "Weight strategy not implemented"
|
1429
1436
|
if weight_strat:
|
1430
1437
|
if weight_strat == "eq_weights":
|
1431
1438
|
self.weights = [1.0 / self.item_count] * self.item_count
|
1432
1439
|
elif weight_strat == "inv_vol":
|
1433
|
-
vol = divide(1.0, std(
|
1440
|
+
vol = divide(1.0, std(returns, axis=0, ddof=1))
|
1434
1441
|
vol[isinf(vol)] = nan
|
1435
1442
|
self.weights = list(divide(vol, vol.sum()))
|
1436
1443
|
else:
|
1437
1444
|
raise NotImplementedError(msg)
|
1438
1445
|
|
1439
1446
|
return DataFrame(
|
1440
|
-
data=(
|
1447
|
+
data=(returns @ self.weights).add(1.0).cumprod(),
|
1441
1448
|
index=self.tsdf.index,
|
1442
1449
|
columns=[[name], [ValueType.PRICE]],
|
1443
1450
|
dtype="float64",
|
@@ -1492,13 +1499,11 @@ class OpenFrame(_CommonModel):
|
|
1492
1499
|
)
|
1493
1500
|
|
1494
1501
|
retseries = (
|
1495
|
-
relative.pct_change(
|
1496
|
-
.rolling(observations, min_periods=observations)
|
1497
|
-
.sum()
|
1502
|
+
relative.pct_change().rolling(observations, min_periods=observations).sum()
|
1498
1503
|
)
|
1499
1504
|
retdf = retseries.dropna().to_frame()
|
1500
1505
|
|
1501
|
-
voldf = relative.pct_change(
|
1506
|
+
voldf = relative.pct_change().rolling(
|
1502
1507
|
observations,
|
1503
1508
|
min_periods=observations,
|
1504
1509
|
).std() * sqrt(time_factor)
|
@@ -1542,7 +1547,7 @@ class OpenFrame(_CommonModel):
|
|
1542
1547
|
asset_label = cast(tuple[str, str], self.tsdf.iloc[:, asset_column].name)[0]
|
1543
1548
|
beta_label = f"{asset_label} / {market_label}"
|
1544
1549
|
|
1545
|
-
rolling = self.tsdf.pct_change(
|
1550
|
+
rolling = self.tsdf.pct_change().rolling(
|
1546
1551
|
observations,
|
1547
1552
|
min_periods=observations,
|
1548
1553
|
)
|
@@ -1599,12 +1604,10 @@ class OpenFrame(_CommonModel):
|
|
1599
1604
|
)
|
1600
1605
|
first_series = (
|
1601
1606
|
self.tsdf.iloc[:, first_column]
|
1602
|
-
.pct_change(
|
1607
|
+
.pct_change()[1:]
|
1603
1608
|
.rolling(observations, min_periods=observations)
|
1604
1609
|
)
|
1605
|
-
second_series = self.tsdf.iloc[:, second_column].pct_change(
|
1606
|
-
fill_method=None,
|
1607
|
-
)[1:]
|
1610
|
+
second_series = self.tsdf.iloc[:, second_column].pct_change()[1:]
|
1608
1611
|
corrdf = first_series.corr(other=second_series).dropna().to_frame()
|
1609
1612
|
corrdf.columns = MultiIndex.from_arrays(
|
1610
1613
|
[
|
openseries/portfoliotools.py
CHANGED
@@ -83,13 +83,15 @@ def simulate_portfolios(
|
|
83
83
|
"""
|
84
84
|
copi = simframe.from_deepcopy()
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
):
|
86
|
+
vtypes = [x == ValueType.RTRN for x in copi.tsdf.columns.get_level_values(1)]
|
87
|
+
if not any(vtypes):
|
89
88
|
copi.value_to_ret()
|
90
89
|
log_ret = copi.tsdf.copy()[1:]
|
91
|
-
|
90
|
+
elif all(vtypes):
|
92
91
|
log_ret = copi.tsdf.copy()
|
92
|
+
else:
|
93
|
+
msg = "Mix of series types will give inconsistent results"
|
94
|
+
raise ValueError(msg)
|
93
95
|
|
94
96
|
log_ret.columns = log_ret.columns.droplevel(level=1)
|
95
97
|
|
@@ -105,15 +107,14 @@ def simulate_portfolios(
|
|
105
107
|
weights = weights / npsum(weights)
|
106
108
|
all_weights[x, :] = weights
|
107
109
|
|
108
|
-
vol_arr[x] = sqrt(
|
109
|
-
|
110
|
+
vol_arr[x] = sqrt(
|
111
|
+
weights.T @ (log_ret.cov() * simframe.periods_in_a_year @ weights),
|
110
112
|
)
|
111
113
|
|
112
114
|
ret_arr[x] = npsum(log_ret.mean() * weights * simframe.periods_in_a_year)
|
113
115
|
|
114
116
|
sharpe_arr[x] = ret_arr[x] / vol_arr[x]
|
115
117
|
|
116
|
-
# noinspection PyUnreachableCode
|
117
118
|
simdf = concat(
|
118
119
|
[
|
119
120
|
DataFrame({"stdev": vol_arr, "ret": ret_arr, "sharpe": sharpe_arr}),
|
@@ -166,13 +167,15 @@ def efficient_frontier( # noqa: C901
|
|
166
167
|
|
167
168
|
copi = eframe.from_deepcopy()
|
168
169
|
|
169
|
-
|
170
|
-
|
171
|
-
):
|
170
|
+
vtypes = [x == ValueType.RTRN for x in copi.tsdf.columns.get_level_values(1)]
|
171
|
+
if not any(vtypes):
|
172
172
|
copi.value_to_ret()
|
173
173
|
log_ret = copi.tsdf.copy()[1:]
|
174
|
-
|
174
|
+
elif all(vtypes):
|
175
175
|
log_ret = copi.tsdf.copy()
|
176
|
+
else:
|
177
|
+
msg = "Mix of series types will give inconsistent results"
|
178
|
+
raise ValueError(msg)
|
176
179
|
|
177
180
|
log_ret.columns = log_ret.columns.droplevel(level=1)
|
178
181
|
|
@@ -284,7 +287,6 @@ def efficient_frontier( # noqa: C901
|
|
284
287
|
frontier_x.append(result["fun"])
|
285
288
|
frontier_weights.append(result["x"])
|
286
289
|
|
287
|
-
# noinspection PyUnreachableCode
|
288
290
|
line_df = concat(
|
289
291
|
[
|
290
292
|
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
|
@@ -348,6 +346,7 @@ class OpenTimeSeries(_CommonModel):
|
|
348
346
|
- cast(DatetimeIndex, d_range)[:-1]
|
349
347
|
],
|
350
348
|
)
|
349
|
+
# noinspection PyTypeChecker
|
351
350
|
arr = list(cumprod(insert(1 + deltas * rate / 365, 0, 1.0)))
|
352
351
|
dates = [d.strftime("%Y-%m-%d") for d in cast(DatetimeIndex, d_range)]
|
353
352
|
|
@@ -436,15 +435,12 @@ class OpenTimeSeries(_CommonModel):
|
|
436
435
|
The returns of the values in the series
|
437
436
|
|
438
437
|
"""
|
439
|
-
|
440
|
-
|
438
|
+
returns = self.tsdf.ffill().pct_change()
|
439
|
+
returns.iloc[0] = 0
|
441
440
|
self.valuetype = ValueType.RTRN
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
[self.valuetype],
|
446
|
-
],
|
447
|
-
)
|
441
|
+
arrays = [[self.label], [self.valuetype]]
|
442
|
+
returns.columns = MultiIndex.from_arrays(arrays=arrays)
|
443
|
+
self.tsdf = returns.copy()
|
448
444
|
return self
|
449
445
|
|
450
446
|
def value_to_diff(self: Self, periods: int = 1) -> Self:
|
@@ -482,14 +478,12 @@ class OpenTimeSeries(_CommonModel):
|
|
482
478
|
An OpenTimeSeries object
|
483
479
|
|
484
480
|
"""
|
485
|
-
if
|
486
|
-
x == ValueType.RTRN
|
487
|
-
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).to_numpy()
|
488
|
-
):
|
481
|
+
if self.valuetype == ValueType.PRICE:
|
489
482
|
self.value_to_ret()
|
490
483
|
|
491
484
|
self.tsdf = self.tsdf.add(1.0)
|
492
485
|
self.tsdf = self.tsdf.cumprod(axis=0) / self.tsdf.iloc[0]
|
486
|
+
|
493
487
|
self.valuetype = ValueType.PRICE
|
494
488
|
self.tsdf.columns = MultiIndex.from_arrays(
|
495
489
|
[
|
@@ -522,6 +516,7 @@ class OpenTimeSeries(_CommonModel):
|
|
522
516
|
arr = array(self.values) / divider
|
523
517
|
|
524
518
|
deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
|
519
|
+
# noinspection PyTypeChecker
|
525
520
|
arr = cumprod(insert(1.0 + deltas * arr[:-1] / days_in_year, 0, 1.0))
|
526
521
|
|
527
522
|
self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index]
|
@@ -686,17 +681,13 @@ class OpenTimeSeries(_CommonModel):
|
|
686
681
|
An OpenTimeSeries object
|
687
682
|
|
688
683
|
"""
|
689
|
-
|
690
|
-
if any(
|
691
|
-
x == ValueType.RTRN
|
692
|
-
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).to_numpy()
|
693
|
-
):
|
684
|
+
if self.valuetype == ValueType.RTRN:
|
694
685
|
ra_df = self.tsdf.copy()
|
695
686
|
values = [1.0]
|
696
687
|
returns_input = True
|
697
688
|
else:
|
698
689
|
values = [cast(float, self.tsdf.iloc[0, 0])]
|
699
|
-
ra_df = self.tsdf.pct_change(
|
690
|
+
ra_df = self.tsdf.pct_change()
|
700
691
|
returns_input = False
|
701
692
|
ra_df = ra_df.dropna()
|
702
693
|
|
@@ -820,6 +811,7 @@ def timeseries_chain(
|
|
820
811
|
|
821
812
|
dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
|
822
813
|
|
814
|
+
# noinspection PyUnresolvedReferences
|
823
815
|
if back.__class__.__subclasscheck__(
|
824
816
|
OpenTimeSeries,
|
825
817
|
):
|
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.4
|
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=kCP2UWRwnlh4Byj6qUKEddW5HhSh6z5tD6xinJNNtQE,72863
|
3
|
+
openseries/_risk.py,sha256=PReIfkzhInvIgJkzI4k1wYvhmLZ4cCurYKuQAvlHLlE,2082
|
4
|
+
openseries/datefixer.py,sha256=3E8Ddf3Ps9k5qUWqL13ONelyMECZsvtyrsFJg7r3bxE,12298
|
5
|
+
openseries/frame.py,sha256=3f-vXWhQgmApJ4FSLYXydHSxutI1uT0dLJSF9zASlfs,54784
|
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=pyCXnNLTnd9MGtmRIJZ-bz4fccOuUsXSX-3AbSpwa2Y,19094
|
10
|
+
openseries/series.py,sha256=NSL3wFqoXgGLlGX-9t6QJo3QPOXJWV7yAn2_oIY-SaU,27646
|
11
|
+
openseries/simulation.py,sha256=jghKf0d2deXK-rqn8BeJ_s3JQVLL9FuE_IW29Kf1_-k,13816
|
12
|
+
openseries/types.py,sha256=IbzW9iVuA3MEx3WtjyB6QYeSd3TkGjTOkFnnpSADKlY,7491
|
13
|
+
openseries-1.7.4.dist-info/LICENSE.md,sha256=IQ8_IMXgHxyv4M48G14fJsjcrkiSASdalASTXWCOsj4,1515
|
14
|
+
openseries-1.7.4.dist-info/METADATA,sha256=Is5FxVodV6ix3yUHuljPgP8fP3EAtU3H4u5h9Q-RCRE,43973
|
15
|
+
openseries-1.7.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
16
|
+
openseries-1.7.4.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
|