openseries 1.7.3__tar.gz → 1.7.4__tar.gz
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-1.7.3 → openseries-1.7.4}/PKG-INFO +1 -1
- {openseries-1.7.3 → openseries-1.7.4}/openseries/_common_model.py +7 -3
- {openseries-1.7.3 → openseries-1.7.4}/openseries/frame.py +61 -44
- {openseries-1.7.3 → openseries-1.7.4}/openseries/portfoliotools.py +12 -8
- {openseries-1.7.3 → openseries-1.7.4}/openseries/series.py +11 -17
- {openseries-1.7.3 → openseries-1.7.4}/pyproject.toml +4 -4
- {openseries-1.7.3 → openseries-1.7.4}/LICENSE.md +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/README.md +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/__init__.py +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/_risk.py +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/datefixer.py +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/load_plotly.py +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/plotly_captor_logo.json +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/plotly_layouts.json +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/simulation.py +0 -0
- {openseries-1.7.3 → openseries-1.7.4}/openseries/types.py +0 -0
@@ -59,6 +59,7 @@ from .types import (
|
|
59
59
|
)
|
60
60
|
|
61
61
|
|
62
|
+
# noinspection PyTypeChecker
|
62
63
|
class _CommonModel(BaseModel):
|
63
64
|
"""Declare _CommonModel."""
|
64
65
|
|
@@ -680,7 +681,7 @@ class _CommonModel(BaseModel):
|
|
680
681
|
output.append(dict(itemdata))
|
681
682
|
|
682
683
|
with dirpath.joinpath(filename).open(mode="w", encoding="utf-8") as jsonfile:
|
683
|
-
dump(output, jsonfile, indent=2, sort_keys=False)
|
684
|
+
dump(obj=output, fp=jsonfile, indent=2, sort_keys=False)
|
684
685
|
|
685
686
|
return output
|
686
687
|
|
@@ -1027,7 +1028,10 @@ class _CommonModel(BaseModel):
|
|
1027
1028
|
time_factor = how_many / fraction
|
1028
1029
|
|
1029
1030
|
result = (
|
1030
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1031
|
+
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1032
|
+
.ffill()
|
1033
|
+
.pct_change()
|
1034
|
+
.mean()
|
1031
1035
|
* time_factor
|
1032
1036
|
)
|
1033
1037
|
|
@@ -1085,7 +1089,7 @@ class _CommonModel(BaseModel):
|
|
1085
1089
|
time_factor = how_many / fraction
|
1086
1090
|
|
1087
1091
|
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1088
|
-
result = data.pct_change().std().mul(sqrt(time_factor))
|
1092
|
+
result = data.ffill().pct_change().std().mul(sqrt(time_factor))
|
1089
1093
|
|
1090
1094
|
if self.tsdf.shape[1] == 1:
|
1091
1095
|
return float(cast(SupportsFloat, result.iloc[0]))
|
@@ -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)
|
@@ -453,8 +459,15 @@ class OpenFrame(_CommonModel):
|
|
453
459
|
method=method,
|
454
460
|
)
|
455
461
|
|
462
|
+
arrays = [
|
463
|
+
self.tsdf.columns.get_level_values(0),
|
464
|
+
self.tsdf.columns.get_level_values(1),
|
465
|
+
]
|
466
|
+
|
456
467
|
self._set_tsdf()
|
457
468
|
|
469
|
+
self.tsdf.columns = MultiIndex.from_arrays(arrays)
|
470
|
+
|
458
471
|
return self
|
459
472
|
|
460
473
|
def ewma_risk(
|
@@ -1284,30 +1297,8 @@ class OpenFrame(_CommonModel):
|
|
1284
1297
|
|
1285
1298
|
"""
|
1286
1299
|
full_year = 1.0
|
1287
|
-
|
1288
|
-
|
1289
|
-
for x in self.tsdf.columns.get_level_values(1).to_numpy()
|
1290
|
-
):
|
1291
|
-
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1292
|
-
if isinstance(asset, tuple):
|
1293
|
-
asset_log = self.tsdf.loc[:, asset]
|
1294
|
-
asset_cagr = asset_log.mean()
|
1295
|
-
elif isinstance(asset, int):
|
1296
|
-
asset_log = self.tsdf.iloc[:, asset]
|
1297
|
-
asset_cagr = asset_log.mean()
|
1298
|
-
else:
|
1299
|
-
raise TypeError(msg)
|
1300
|
-
|
1301
|
-
msg = "market should be a tuple[str, ValueType] or an integer."
|
1302
|
-
if isinstance(market, tuple):
|
1303
|
-
market_log = self.tsdf.loc[:, market]
|
1304
|
-
market_cagr = market_log.mean()
|
1305
|
-
elif isinstance(market, int):
|
1306
|
-
market_log = self.tsdf.iloc[:, market]
|
1307
|
-
market_cagr = market_log.mean()
|
1308
|
-
else:
|
1309
|
-
raise TypeError(msg)
|
1310
|
-
else:
|
1300
|
+
vtypes = [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)]
|
1301
|
+
if not any(vtypes):
|
1311
1302
|
msg = "asset should be a tuple[str, ValueType] or an integer."
|
1312
1303
|
if isinstance(asset, tuple):
|
1313
1304
|
asset_log = log(
|
@@ -1375,6 +1366,29 @@ class OpenFrame(_CommonModel):
|
|
1375
1366
|
)
|
1376
1367
|
else:
|
1377
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)
|
1378
1392
|
|
1379
1393
|
covariance = cov(asset_log, market_log, ddof=dlta_degr_freedms)
|
1380
1394
|
beta = covariance[0, 1] / covariance[1, 1]
|
@@ -1407,27 +1421,30 @@ class OpenFrame(_CommonModel):
|
|
1407
1421
|
"to run the make_portfolio method."
|
1408
1422
|
)
|
1409
1423
|
raise ValueError(msg)
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
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)
|
1417
1434
|
|
1418
1435
|
msg = "Weight strategy not implemented"
|
1419
1436
|
if weight_strat:
|
1420
1437
|
if weight_strat == "eq_weights":
|
1421
1438
|
self.weights = [1.0 / self.item_count] * self.item_count
|
1422
1439
|
elif weight_strat == "inv_vol":
|
1423
|
-
vol = divide(1.0, std(
|
1440
|
+
vol = divide(1.0, std(returns, axis=0, ddof=1))
|
1424
1441
|
vol[isinf(vol)] = nan
|
1425
1442
|
self.weights = list(divide(vol, vol.sum()))
|
1426
1443
|
else:
|
1427
1444
|
raise NotImplementedError(msg)
|
1428
1445
|
|
1429
1446
|
return DataFrame(
|
1430
|
-
data=(
|
1447
|
+
data=(returns @ self.weights).add(1.0).cumprod(),
|
1431
1448
|
index=self.tsdf.index,
|
1432
1449
|
columns=[[name], [ValueType.PRICE]],
|
1433
1450
|
dtype="float64",
|
@@ -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
|
|
@@ -165,13 +167,15 @@ def efficient_frontier( # noqa: C901
|
|
165
167
|
|
166
168
|
copi = eframe.from_deepcopy()
|
167
169
|
|
168
|
-
|
169
|
-
|
170
|
-
):
|
170
|
+
vtypes = [x == ValueType.RTRN for x in copi.tsdf.columns.get_level_values(1)]
|
171
|
+
if not any(vtypes):
|
171
172
|
copi.value_to_ret()
|
172
173
|
log_ret = copi.tsdf.copy()[1:]
|
173
|
-
|
174
|
+
elif all(vtypes):
|
174
175
|
log_ret = copi.tsdf.copy()
|
176
|
+
else:
|
177
|
+
msg = "Mix of series types will give inconsistent results"
|
178
|
+
raise ValueError(msg)
|
175
179
|
|
176
180
|
log_ret.columns = log_ret.columns.droplevel(level=1)
|
177
181
|
|
@@ -346,6 +346,7 @@ class OpenTimeSeries(_CommonModel):
|
|
346
346
|
- cast(DatetimeIndex, d_range)[:-1]
|
347
347
|
],
|
348
348
|
)
|
349
|
+
# noinspection PyTypeChecker
|
349
350
|
arr = list(cumprod(insert(1 + deltas * rate / 365, 0, 1.0)))
|
350
351
|
dates = [d.strftime("%Y-%m-%d") for d in cast(DatetimeIndex, d_range)]
|
351
352
|
|
@@ -434,15 +435,12 @@ class OpenTimeSeries(_CommonModel):
|
|
434
435
|
The returns of the values in the series
|
435
436
|
|
436
437
|
"""
|
437
|
-
|
438
|
-
|
438
|
+
returns = self.tsdf.ffill().pct_change()
|
439
|
+
returns.iloc[0] = 0
|
439
440
|
self.valuetype = ValueType.RTRN
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
[self.valuetype],
|
444
|
-
],
|
445
|
-
)
|
441
|
+
arrays = [[self.label], [self.valuetype]]
|
442
|
+
returns.columns = MultiIndex.from_arrays(arrays=arrays)
|
443
|
+
self.tsdf = returns.copy()
|
446
444
|
return self
|
447
445
|
|
448
446
|
def value_to_diff(self: Self, periods: int = 1) -> Self:
|
@@ -480,14 +478,12 @@ class OpenTimeSeries(_CommonModel):
|
|
480
478
|
An OpenTimeSeries object
|
481
479
|
|
482
480
|
"""
|
483
|
-
if
|
484
|
-
x == ValueType.RTRN
|
485
|
-
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).to_numpy()
|
486
|
-
):
|
481
|
+
if self.valuetype == ValueType.PRICE:
|
487
482
|
self.value_to_ret()
|
488
483
|
|
489
484
|
self.tsdf = self.tsdf.add(1.0)
|
490
485
|
self.tsdf = self.tsdf.cumprod(axis=0) / self.tsdf.iloc[0]
|
486
|
+
|
491
487
|
self.valuetype = ValueType.PRICE
|
492
488
|
self.tsdf.columns = MultiIndex.from_arrays(
|
493
489
|
[
|
@@ -520,6 +516,7 @@ class OpenTimeSeries(_CommonModel):
|
|
520
516
|
arr = array(self.values) / divider
|
521
517
|
|
522
518
|
deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
|
519
|
+
# noinspection PyTypeChecker
|
523
520
|
arr = cumprod(insert(1.0 + deltas * arr[:-1] / days_in_year, 0, 1.0))
|
524
521
|
|
525
522
|
self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index]
|
@@ -684,11 +681,7 @@ class OpenTimeSeries(_CommonModel):
|
|
684
681
|
An OpenTimeSeries object
|
685
682
|
|
686
683
|
"""
|
687
|
-
|
688
|
-
if any(
|
689
|
-
x == ValueType.RTRN
|
690
|
-
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).to_numpy()
|
691
|
-
):
|
684
|
+
if self.valuetype == ValueType.RTRN:
|
692
685
|
ra_df = self.tsdf.copy()
|
693
686
|
values = [1.0]
|
694
687
|
returns_input = True
|
@@ -818,6 +811,7 @@ def timeseries_chain(
|
|
818
811
|
|
819
812
|
dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
|
820
813
|
|
814
|
+
# noinspection PyUnresolvedReferences
|
821
815
|
if back.__class__.__subclasscheck__(
|
822
816
|
OpenTimeSeries,
|
823
817
|
):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "openseries"
|
3
|
-
version = "1.7.
|
3
|
+
version = "1.7.4"
|
4
4
|
description = "Tools for analyzing financial timeseries."
|
5
5
|
authors = ["Martin Karrin <martin.karrin@captor.se>"]
|
6
6
|
repository = "https://github.com/CaptorAB/openseries"
|
@@ -51,9 +51,9 @@ coverage = ">=7.6.0,<8.0.0"
|
|
51
51
|
genbadge = {version = ">=1.1.1,<2.0.0", extras = ["coverage"]}
|
52
52
|
mypy = "^1.11.2"
|
53
53
|
pandas-stubs = ">=2.1.2,<3.0.0"
|
54
|
-
pre-commit = ">=3.7.1,<
|
54
|
+
pre-commit = ">=3.7.1,<6.0.0"
|
55
55
|
pytest = ">=8.2.2,<9.0.0"
|
56
|
-
ruff = "^0.6.
|
56
|
+
ruff = "^0.6.9"
|
57
57
|
types-openpyxl = ">=3.1.2,<4.0.0"
|
58
58
|
types-python-dateutil = ">=2.8.2,<3.0.0"
|
59
59
|
types-requests = ">=2.20.0,<3.0.0"
|
@@ -110,7 +110,7 @@ fixable = ["ALL"]
|
|
110
110
|
|
111
111
|
[tool.ruff.lint.pylint]
|
112
112
|
max-args = 12
|
113
|
-
max-branches =
|
113
|
+
max-branches = 23
|
114
114
|
max-statements = 59
|
115
115
|
|
116
116
|
[tool.pytest.ini_options]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|