openseries 2.1.5__tar.gz → 2.1.7__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-2.1.5 → openseries-2.1.7}/PKG-INFO +4 -3
- {openseries-2.1.5 → openseries-2.1.7}/README.md +2 -2
- {openseries-2.1.5 → openseries-2.1.7}/openseries/_common_model.py +89 -3
- {openseries-2.1.5 → openseries-2.1.7}/openseries/datefixer.py +28 -6
- {openseries-2.1.5 → openseries-2.1.7}/openseries/frame.py +1 -2
- {openseries-2.1.5 → openseries-2.1.7}/openseries/owntypes.py +10 -3
- {openseries-2.1.5 → openseries-2.1.7}/openseries/portfoliotools.py +5 -3
- {openseries-2.1.5 → openseries-2.1.7}/openseries/report.py +14 -12
- {openseries-2.1.5 → openseries-2.1.7}/openseries/series.py +236 -3
- {openseries-2.1.5 → openseries-2.1.7}/openseries/simulation.py +74 -18
- {openseries-2.1.5 → openseries-2.1.7}/pyproject.toml +5 -4
- {openseries-2.1.5 → openseries-2.1.7}/LICENSE.md +0 -0
- {openseries-2.1.5 → openseries-2.1.7}/openseries/__init__.py +0 -0
- {openseries-2.1.5 → openseries-2.1.7}/openseries/_risk.py +0 -0
- {openseries-2.1.5 → openseries-2.1.7}/openseries/html_utils.py +0 -0
- {openseries-2.1.5 → openseries-2.1.7}/openseries/load_plotly.py +0 -0
- {openseries-2.1.5 → openseries-2.1.7}/openseries/plotly_captor_logo.json +0 -0
- {openseries-2.1.5 → openseries-2.1.7}/openseries/plotly_layouts.json +0 -0
- {openseries-2.1.5 → openseries-2.1.7}/openseries/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openseries
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.7
|
|
4
4
|
Summary: Tools for analyzing financial timeseries.
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -31,6 +31,7 @@ Requires-Dist: python-dateutil (>=2.8.2)
|
|
|
31
31
|
Requires-Dist: requests (>=2.20.0)
|
|
32
32
|
Requires-Dist: scikit-learn (>=1.4.0)
|
|
33
33
|
Requires-Dist: scipy (>=1.14.1)
|
|
34
|
+
Requires-Dist: tzdata (>=2025.3)
|
|
34
35
|
Project-URL: Documentation, https://openseries.readthedocs.io/
|
|
35
36
|
Project-URL: Homepage, https://captorab.github.io/openseries/
|
|
36
37
|
Project-URL: Issue Tracker, https://github.com/CaptorAB/openseries/issues
|
|
@@ -50,7 +51,7 @@ Description-Content-Type: text/markdown
|
|
|
50
51
|
[](https://www.python.org/)
|
|
51
52
|
[](https://github.com/CaptorAB/openseries/actions/workflows/test.yml)
|
|
52
53
|
[](https://codecov.io/gh/CaptorAB/openseries/branch/master)
|
|
53
|
-

|
|
54
|
+
[](https://captorab.github.io/openseries/)
|
|
54
55
|
[](https://python-poetry.org/)
|
|
55
56
|
[](https://beta.ruff.rs/docs/)
|
|
56
57
|
[](https://github.com/CaptorAB/openseries/blob/master/LICENSE.md)
|
|
@@ -60,7 +61,7 @@ Tools for analyzing financial timeseries of a single asset or a group of assets.
|
|
|
60
61
|
|
|
61
62
|
## Documentation
|
|
62
63
|
|
|
63
|
-
Complete documentation is available at: [https://
|
|
64
|
+
Complete documentation is available at: [https://captorab.github.io/openseries/](https://captorab.github.io/openseries/)
|
|
64
65
|
|
|
65
66
|
The documentation includes:
|
|
66
67
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://www.python.org/)
|
|
11
11
|
[](https://github.com/CaptorAB/openseries/actions/workflows/test.yml)
|
|
12
12
|
[](https://codecov.io/gh/CaptorAB/openseries/branch/master)
|
|
13
|
-

|
|
13
|
+
[](https://captorab.github.io/openseries/)
|
|
14
14
|
[](https://python-poetry.org/)
|
|
15
15
|
[](https://beta.ruff.rs/docs/)
|
|
16
16
|
[](https://github.com/CaptorAB/openseries/blob/master/LICENSE.md)
|
|
@@ -20,7 +20,7 @@ Tools for analyzing financial timeseries of a single asset or a group of assets.
|
|
|
20
20
|
|
|
21
21
|
## Documentation
|
|
22
22
|
|
|
23
|
-
Complete documentation is available at: [https://
|
|
23
|
+
Complete documentation is available at: [https://captorab.github.io/openseries/](https://captorab.github.io/openseries/)
|
|
24
24
|
|
|
25
25
|
The documentation includes:
|
|
26
26
|
|
|
@@ -162,6 +162,30 @@ def _get_base_column_data(
|
|
|
162
162
|
return data, item, label
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
def _demeaned_returns_for_autocorr(
|
|
166
|
+
series: Series[float], valuetype: ValueType, *, squared: bool = False
|
|
167
|
+
) -> Series[float]:
|
|
168
|
+
"""Return demeaned return series for autocorrelation analysis.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
series: Input series (prices or returns).
|
|
172
|
+
valuetype: ValueType.PRICE for price data (pct_change applied),
|
|
173
|
+
else use as returns.
|
|
174
|
+
squared: If True, square the demeaned returns.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Demeaned return series (optionally squared).
|
|
178
|
+
"""
|
|
179
|
+
if valuetype == ValueType.PRICE:
|
|
180
|
+
rets = series.ffill().pct_change().dropna()
|
|
181
|
+
else:
|
|
182
|
+
rets = series.ffill().dropna()
|
|
183
|
+
rets = rets - rets.mean()
|
|
184
|
+
if squared:
|
|
185
|
+
rets = rets**2
|
|
186
|
+
return rets
|
|
187
|
+
|
|
188
|
+
|
|
165
189
|
def _calculate_time_factor(
|
|
166
190
|
data: Series[float],
|
|
167
191
|
earlier: dt.date,
|
|
@@ -282,8 +306,9 @@ class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]):
|
|
|
282
306
|
result = (
|
|
283
307
|
self.tsdf.groupby(years)
|
|
284
308
|
.apply(
|
|
285
|
-
lambda prices: (
|
|
286
|
-
|
|
309
|
+
lambda prices: (
|
|
310
|
+
(prices / prices.expanding(min_periods=1).max()).min() - 1
|
|
311
|
+
),
|
|
287
312
|
)
|
|
288
313
|
.min()
|
|
289
314
|
)
|
|
@@ -353,6 +378,23 @@ class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]):
|
|
|
353
378
|
"""
|
|
354
379
|
return self.vol_func()
|
|
355
380
|
|
|
381
|
+
@property
|
|
382
|
+
def autocorr(self: Self) -> SeriesOrFloat_co:
|
|
383
|
+
"""Autocorrelation at lag 1.
|
|
384
|
+
|
|
385
|
+
Shorthand for ``autocorr_func(lag=1)``. Returns the lag-1 autocorrelation
|
|
386
|
+
of demeaned returns. For price series, returns are computed via
|
|
387
|
+
``pct_change``; for return series, raw values are used after demeaning.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
--------
|
|
391
|
+
SeriesOrFloat_co
|
|
392
|
+
Autocorrelation at lag 1.
|
|
393
|
+
Returns float for OpenTimeSeries, Series[float] for OpenFrame.
|
|
394
|
+
|
|
395
|
+
"""
|
|
396
|
+
return self.autocorr_func()
|
|
397
|
+
|
|
356
398
|
@property
|
|
357
399
|
def downside_deviation(self: Self) -> SeriesOrFloat_co:
|
|
358
400
|
"""Downside Deviation.
|
|
@@ -823,7 +865,7 @@ class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]):
|
|
|
823
865
|
else None,
|
|
824
866
|
)
|
|
825
867
|
]
|
|
826
|
-
self.tsdf = self.tsdf.reindex(labels=d_range, method=method
|
|
868
|
+
self.tsdf = self.tsdf.reindex(labels=d_range, method=method)
|
|
827
869
|
|
|
828
870
|
return self
|
|
829
871
|
|
|
@@ -1481,6 +1523,50 @@ class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]):
|
|
|
1481
1523
|
|
|
1482
1524
|
return self._coerce_result(result=result, name="Volatility")
|
|
1483
1525
|
|
|
1526
|
+
def autocorr_func(
|
|
1527
|
+
self: Self,
|
|
1528
|
+
lag: int = 1,
|
|
1529
|
+
*,
|
|
1530
|
+
squared: bool = False,
|
|
1531
|
+
) -> SeriesOrFloat_co:
|
|
1532
|
+
"""Calculate autocorrelation at a given lag.
|
|
1533
|
+
|
|
1534
|
+
Computes the autocorrelation of demeaned returns at the specified lag.
|
|
1535
|
+
For price series (ValueType.PRICE), returns are derived via ``pct_change``;
|
|
1536
|
+
for return series (ValueType.RTRN), raw values are demeaned. Use
|
|
1537
|
+
``squared=True`` for squared-return autocorrelation (e.g. volatility
|
|
1538
|
+
clustering). Returns ``nan`` when the series has too few observations.
|
|
1539
|
+
|
|
1540
|
+
Args:
|
|
1541
|
+
lag: The lag at which to compute autocorrelation. Defaults to 1.
|
|
1542
|
+
squared: If True, compute autocorrelation of squared returns.
|
|
1543
|
+
Defaults to False.
|
|
1544
|
+
|
|
1545
|
+
Returns:
|
|
1546
|
+
Autocorrelation at the specified lag. Float for OpenTimeSeries,
|
|
1547
|
+
``Series[float]`` for OpenFrame.
|
|
1548
|
+
"""
|
|
1549
|
+
values: list[float] = []
|
|
1550
|
+
vtypes = self.tsdf.columns.get_level_values(1)
|
|
1551
|
+
for col_idx, col in enumerate(self.tsdf.columns):
|
|
1552
|
+
valuetype = cast("ValueType", vtypes[col_idx])
|
|
1553
|
+
rets = _demeaned_returns_for_autocorr(
|
|
1554
|
+
series=self.tsdf[col],
|
|
1555
|
+
valuetype=valuetype,
|
|
1556
|
+
squared=squared,
|
|
1557
|
+
)
|
|
1558
|
+
if len(rets) > lag:
|
|
1559
|
+
values.append(float(rets.autocorr(lag=lag)))
|
|
1560
|
+
else:
|
|
1561
|
+
values.append(float("nan"))
|
|
1562
|
+
result = Series(
|
|
1563
|
+
data=values,
|
|
1564
|
+
index=self.tsdf.columns,
|
|
1565
|
+
name="Autocorrelation",
|
|
1566
|
+
dtype="float64",
|
|
1567
|
+
)
|
|
1568
|
+
return self._coerce_result(result=result, name="Autocorrelation")
|
|
1569
|
+
|
|
1484
1570
|
def vol_from_var_func(
|
|
1485
1571
|
self: Self,
|
|
1486
1572
|
level: float = 0.95,
|
|
@@ -374,13 +374,22 @@ def generate_calendar_date_range(
|
|
|
374
374
|
raise TradingDaysNotAboveZeroError(msg)
|
|
375
375
|
|
|
376
376
|
if start and not end:
|
|
377
|
+
adjusted_start = date_offset_foll(
|
|
378
|
+
raw_date=start,
|
|
379
|
+
months_offset=0,
|
|
380
|
+
countries=countries,
|
|
381
|
+
markets=markets,
|
|
382
|
+
custom_holidays=custom_holidays,
|
|
383
|
+
adjust=True,
|
|
384
|
+
following=True,
|
|
385
|
+
)
|
|
377
386
|
tmp_range = date_range(
|
|
378
|
-
start=
|
|
387
|
+
start=adjusted_start,
|
|
379
388
|
periods=trading_days * 365 // 252,
|
|
380
389
|
freq="D",
|
|
381
390
|
)
|
|
382
391
|
calendar = holiday_calendar(
|
|
383
|
-
startyear=
|
|
392
|
+
startyear=adjusted_start.year,
|
|
384
393
|
endyear=date_fix(tmp_range.tolist()[-1]).year,
|
|
385
394
|
countries=countries,
|
|
386
395
|
markets=markets,
|
|
@@ -389,17 +398,30 @@ def generate_calendar_date_range(
|
|
|
389
398
|
return [
|
|
390
399
|
d.date()
|
|
391
400
|
for d in date_range(
|
|
392
|
-
start=
|
|
401
|
+
start=adjusted_start,
|
|
393
402
|
periods=trading_days,
|
|
394
403
|
freq=CustomBusinessDay(calendar=calendar),
|
|
395
404
|
)
|
|
396
405
|
]
|
|
397
406
|
|
|
398
407
|
if end and not start:
|
|
399
|
-
|
|
408
|
+
adjusted_end = date_offset_foll(
|
|
409
|
+
raw_date=end,
|
|
410
|
+
months_offset=0,
|
|
411
|
+
countries=countries,
|
|
412
|
+
markets=markets,
|
|
413
|
+
custom_holidays=custom_holidays,
|
|
414
|
+
adjust=True,
|
|
415
|
+
following=False,
|
|
416
|
+
)
|
|
417
|
+
tmp_range = date_range(
|
|
418
|
+
end=adjusted_end,
|
|
419
|
+
periods=trading_days * 365 // 252,
|
|
420
|
+
freq="D",
|
|
421
|
+
)
|
|
400
422
|
calendar = holiday_calendar(
|
|
401
423
|
startyear=date_fix(tmp_range.tolist()[0]).year,
|
|
402
|
-
endyear=
|
|
424
|
+
endyear=adjusted_end.year,
|
|
403
425
|
countries=countries,
|
|
404
426
|
markets=markets,
|
|
405
427
|
custom_holidays=custom_holidays,
|
|
@@ -407,7 +429,7 @@ def generate_calendar_date_range(
|
|
|
407
429
|
return [
|
|
408
430
|
d.date()
|
|
409
431
|
for d in date_range(
|
|
410
|
-
end=
|
|
432
|
+
end=adjusted_end,
|
|
411
433
|
periods=trading_days,
|
|
412
434
|
freq=CustomBusinessDay(calendar=calendar),
|
|
413
435
|
)
|
|
@@ -663,13 +663,12 @@ class OpenFrame(_CommonModel[SeriesFloat]):
|
|
|
663
663
|
if not end_cut and where in ["after", "both"]:
|
|
664
664
|
end_cut = self.last_indices.min()
|
|
665
665
|
self.tsdf = self.tsdf.sort_index()
|
|
666
|
-
self.tsdf = self.tsdf.truncate(before=start_cut, after=end_cut
|
|
666
|
+
self.tsdf = self.tsdf.truncate(before=start_cut, after=end_cut)
|
|
667
667
|
|
|
668
668
|
for xerie in self.constituents:
|
|
669
669
|
xerie.tsdf = xerie.tsdf.truncate(
|
|
670
670
|
before=start_cut,
|
|
671
671
|
after=end_cut,
|
|
672
|
-
copy=False,
|
|
673
672
|
)
|
|
674
673
|
if len(set(self.first_indices)) != 1:
|
|
675
674
|
msg = (
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import datetime as dt
|
|
6
|
-
from enum import
|
|
6
|
+
from enum import StrEnum
|
|
7
7
|
from pprint import pformat
|
|
8
8
|
from typing import (
|
|
9
9
|
TYPE_CHECKING,
|
|
@@ -184,11 +184,14 @@ LiteralSeriesProps = Literal[
|
|
|
184
184
|
"span_of_days",
|
|
185
185
|
"yearfrac",
|
|
186
186
|
"periods_in_a_year",
|
|
187
|
+
"autocorr",
|
|
188
|
+
"partial_autocorr",
|
|
187
189
|
]
|
|
188
190
|
LiteralFrameProps = Literal[
|
|
189
191
|
"value_ret",
|
|
190
192
|
"geo_ret",
|
|
191
193
|
"arithmetic_ret",
|
|
194
|
+
"autocorr",
|
|
192
195
|
"vol",
|
|
193
196
|
"downside_deviation",
|
|
194
197
|
"ret_vol_ratio",
|
|
@@ -273,6 +276,8 @@ class OpenTimeSeriesPropertiesList(PropertiesList):
|
|
|
273
276
|
"span_of_days",
|
|
274
277
|
"yearfrac",
|
|
275
278
|
"periods_in_a_year",
|
|
279
|
+
"autocorr",
|
|
280
|
+
"partial_autocorr",
|
|
276
281
|
}
|
|
277
282
|
|
|
278
283
|
def __init__(
|
|
@@ -288,6 +293,7 @@ class OpenFramePropertiesList(PropertiesList):
|
|
|
288
293
|
"""Allowed property arguments for the OpenFrame class."""
|
|
289
294
|
|
|
290
295
|
allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | {
|
|
296
|
+
"autocorr",
|
|
291
297
|
"first_indices",
|
|
292
298
|
"last_indices",
|
|
293
299
|
"lengths_of_items",
|
|
@@ -300,10 +306,11 @@ class OpenFramePropertiesList(PropertiesList):
|
|
|
300
306
|
self._validate()
|
|
301
307
|
|
|
302
308
|
|
|
303
|
-
class ValueType(
|
|
309
|
+
class ValueType(StrEnum):
|
|
304
310
|
"""Enum types of OpenTimeSeries to identify the output."""
|
|
305
311
|
|
|
306
|
-
|
|
312
|
+
EWMA_VOL = "EWMA volatility"
|
|
313
|
+
EWMA_VAR = "EWMA VaR"
|
|
307
314
|
PRICE = "Price(Close)"
|
|
308
315
|
RTRN = "Return(Total)"
|
|
309
316
|
RELRTRN = "Relative return"
|
|
@@ -296,8 +296,10 @@ def _build_frontier_dataframe(
|
|
|
296
296
|
weight_cols = columns_lvl_zero
|
|
297
297
|
weight_header = "<br><br>Weights:<br>"
|
|
298
298
|
line_df["text"] = line_df[weight_cols].apply(
|
|
299
|
-
lambda row:
|
|
300
|
-
|
|
299
|
+
lambda row: (
|
|
300
|
+
weight_header
|
|
301
|
+
+ "<br>".join([f"{row[col]:.1%} {col}" for col in weight_cols])
|
|
302
|
+
),
|
|
301
303
|
axis=1,
|
|
302
304
|
)
|
|
303
305
|
|
|
@@ -781,7 +783,7 @@ def _generate_sharpeplot_output(
|
|
|
781
783
|
)
|
|
782
784
|
return str(plotfile)
|
|
783
785
|
|
|
784
|
-
div_id = filename.split(sep=".")[0]
|
|
786
|
+
div_id = filename.split(maxsplit=1, sep=".")[0]
|
|
785
787
|
return cast(
|
|
786
788
|
"str",
|
|
787
789
|
to_html(
|
|
@@ -586,19 +586,21 @@ def report_html(
|
|
|
586
586
|
|
|
587
587
|
for item, f in zip(rpt_df.index, formats, strict=False):
|
|
588
588
|
rpt_df.loc[item] = rpt_df.loc[item].apply(
|
|
589
|
-
lambda x, fmt=f:
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
str(x)
|
|
597
|
-
if isinstance(x, str)
|
|
589
|
+
lambda x, fmt=f: (
|
|
590
|
+
""
|
|
591
|
+
if (
|
|
592
|
+
x is None
|
|
593
|
+
or (not isinstance(x, str) and isna(x))
|
|
594
|
+
or (isinstance(x, str) and x.lower() in ("nan", "nan%", ""))
|
|
595
|
+
)
|
|
598
596
|
else (
|
|
599
|
-
|
|
600
|
-
if
|
|
601
|
-
else
|
|
597
|
+
str(x)
|
|
598
|
+
if isinstance(x, str)
|
|
599
|
+
else (
|
|
600
|
+
Timestamp(x).strftime("%Y-%m-%d")
|
|
601
|
+
if "%Y-%m-%d" in fmt and not isinstance(x, str)
|
|
602
|
+
else fmt.format(x)
|
|
603
|
+
)
|
|
602
604
|
)
|
|
603
605
|
),
|
|
604
606
|
)
|
|
@@ -32,8 +32,13 @@ from pandas import (
|
|
|
32
32
|
date_range,
|
|
33
33
|
)
|
|
34
34
|
from pydantic import field_validator, model_validator
|
|
35
|
+
from scipy.stats import chi2, norm
|
|
35
36
|
|
|
36
|
-
from ._common_model import
|
|
37
|
+
from ._common_model import (
|
|
38
|
+
_calculate_time_factor,
|
|
39
|
+
_CommonModel,
|
|
40
|
+
_demeaned_returns_for_autocorr,
|
|
41
|
+
)
|
|
37
42
|
from .datefixer import _do_resample_to_business_period_ends, date_fix
|
|
38
43
|
from .owntypes import (
|
|
39
44
|
Countries,
|
|
@@ -415,7 +420,18 @@ class OpenTimeSeries(_CommonModel[float]):
|
|
|
415
420
|
)
|
|
416
421
|
|
|
417
422
|
props = OpenTimeSeriesPropertiesList(*properties)
|
|
418
|
-
|
|
423
|
+
|
|
424
|
+
def _prop_value(name: str) -> float | int | dt.date | Series[float]:
|
|
425
|
+
attr = getattr(self, name)
|
|
426
|
+
return cast(
|
|
427
|
+
"float | int | dt.date | Series[float]",
|
|
428
|
+
attr() if callable(attr) else attr,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
pdf = DataFrame.from_dict(
|
|
432
|
+
{x: _prop_value(x) for x in props},
|
|
433
|
+
orient="index",
|
|
434
|
+
)
|
|
419
435
|
pdf.columns = self.tsdf.columns
|
|
420
436
|
return pdf
|
|
421
437
|
|
|
@@ -647,7 +663,83 @@ class OpenTimeSeries(_CommonModel[float]):
|
|
|
647
663
|
return Series(
|
|
648
664
|
data=rawdata,
|
|
649
665
|
index=data.index,
|
|
650
|
-
name=(self.label, ValueType.
|
|
666
|
+
name=(self.label, ValueType.EWMA_VOL),
|
|
667
|
+
dtype="float64",
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
def ewma_var_func(
|
|
671
|
+
self: Self,
|
|
672
|
+
lmbda: float = 0.94,
|
|
673
|
+
day_chunk: int = 11,
|
|
674
|
+
level: float = 0.95,
|
|
675
|
+
dlta_degr_freedms: int = 0,
|
|
676
|
+
months_from_last: int | None = None,
|
|
677
|
+
from_date: dt.date | None = None,
|
|
678
|
+
to_date: dt.date | None = None,
|
|
679
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
|
680
|
+
) -> Series[float]:
|
|
681
|
+
"""Exponentially Weighted Moving Average Model for Value At Risk (VaR).
|
|
682
|
+
|
|
683
|
+
Reference: https://www.investopedia.com/articles/07/ewma.asp.
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
lmbda: Scaling factor to determine weighting. Defaults to 0.94.
|
|
687
|
+
day_chunk: Sampling the data which is assumed to be daily.
|
|
688
|
+
Defaults to 11.
|
|
689
|
+
level: The sought VaR level. Defaults to 0.95.
|
|
690
|
+
dlta_degr_freedms: Variance bias factor taking the value 0 or 1.
|
|
691
|
+
Defaults to 0.
|
|
692
|
+
months_from_last: Number of months offset as positive integer.
|
|
693
|
+
Overrides use of from_date and to_date. Optional.
|
|
694
|
+
from_date: Specific from date. Optional.
|
|
695
|
+
to_date: Specific to date. Optional.
|
|
696
|
+
periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify
|
|
697
|
+
test cases and comparisons. Optional.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
Series EWMA VaR.
|
|
701
|
+
"""
|
|
702
|
+
earlier, later = self.calc_range(
|
|
703
|
+
months_offset=months_from_last,
|
|
704
|
+
from_dt=from_date,
|
|
705
|
+
to_dt=to_date,
|
|
706
|
+
)
|
|
707
|
+
time_factor = _calculate_time_factor(
|
|
708
|
+
data=self.tsdf.loc[
|
|
709
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
|
710
|
+
].iloc[:, 0],
|
|
711
|
+
earlier=earlier,
|
|
712
|
+
later=later,
|
|
713
|
+
periods_in_a_year_fixed=periods_in_a_year_fixed,
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
data = self.tsdf.loc[
|
|
717
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
|
718
|
+
].copy()
|
|
719
|
+
|
|
720
|
+
data.loc[:, (self.label, ValueType.RTRN)] = log(
|
|
721
|
+
data.loc[:, self.tsdf.columns.to_numpy()[0]],
|
|
722
|
+
).diff()
|
|
723
|
+
|
|
724
|
+
rawdata = [
|
|
725
|
+
data[(self.label, ValueType.RTRN)]
|
|
726
|
+
.iloc[1:day_chunk]
|
|
727
|
+
.std(ddof=dlta_degr_freedms)
|
|
728
|
+
* sqrt(time_factor),
|
|
729
|
+
]
|
|
730
|
+
|
|
731
|
+
for item in data[(self.label, ValueType.RTRN)].iloc[1:]:
|
|
732
|
+
prev = rawdata[-1]
|
|
733
|
+
rawdata.append(
|
|
734
|
+
sqrt(
|
|
735
|
+
square(item) * time_factor * (1 - lmbda) + square(prev) * lmbda,
|
|
736
|
+
),
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
return Series(
|
|
740
|
+
data=array(rawdata) * norm.ppf(1 - level),
|
|
741
|
+
index=data.index,
|
|
742
|
+
name=(self.label, ValueType.EWMA_VAR),
|
|
651
743
|
dtype="float64",
|
|
652
744
|
)
|
|
653
745
|
|
|
@@ -747,6 +839,147 @@ class OpenTimeSeries(_CommonModel[float]):
|
|
|
747
839
|
self.tsdf.columns = self.tsdf.columns.droplevel(level=1)
|
|
748
840
|
return self
|
|
749
841
|
|
|
842
|
+
def _returns_series(self: Self, *, squared: bool = False) -> Series[float]:
|
|
843
|
+
"""Return demeaned return series for autocorrelation analysis."""
|
|
844
|
+
data: Series[float] = self.tsdf.iloc[:, 0]
|
|
845
|
+
return _demeaned_returns_for_autocorr(
|
|
846
|
+
series=data, valuetype=self.valuetype, squared=squared
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
def acf(
|
|
850
|
+
self: Self,
|
|
851
|
+
lags: int | list[int],
|
|
852
|
+
*,
|
|
853
|
+
squared: bool = False,
|
|
854
|
+
) -> Series[float]:
|
|
855
|
+
"""Calculate autocorrelation function for specified lags.
|
|
856
|
+
|
|
857
|
+
Args:
|
|
858
|
+
lags: If int, compute ACF from lag 0 to this value (inclusive).
|
|
859
|
+
If list, compute ACF at lag 0 plus each lag in the list.
|
|
860
|
+
squared: If True, compute ACF of squared returns. Defaults to False.
|
|
861
|
+
|
|
862
|
+
Returns:
|
|
863
|
+
Series of autocorrelations indexed by lag.
|
|
864
|
+
"""
|
|
865
|
+
rets = self._returns_series(squared=squared)
|
|
866
|
+
if isinstance(lags, int):
|
|
867
|
+
lag_list = list(range(lags + 1))
|
|
868
|
+
else:
|
|
869
|
+
lag_list = sorted({0} | set(lags))
|
|
870
|
+
values: list[float] = []
|
|
871
|
+
for lag in lag_list:
|
|
872
|
+
if lag == 0:
|
|
873
|
+
values.append(1.0)
|
|
874
|
+
else:
|
|
875
|
+
values.append(float(rets.autocorr(lag=lag)))
|
|
876
|
+
return Series(
|
|
877
|
+
data=values,
|
|
878
|
+
index=lag_list,
|
|
879
|
+
name="ACF",
|
|
880
|
+
dtype="float64",
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
def partial_autocorr(self: Self, lag: int = 1, *, squared: bool = False) -> float:
|
|
884
|
+
"""Calculate partial autocorrelation at a given lag.
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
lag: The lag at which to compute partial autocorrelation. Defaults to 1.
|
|
888
|
+
squared: If True, compute partial autocorrelation of squared returns.
|
|
889
|
+
Defaults to False.
|
|
890
|
+
|
|
891
|
+
Returns:
|
|
892
|
+
Partial autocorrelation at the specified lag.
|
|
893
|
+
"""
|
|
894
|
+
pacf_series = self.pacf(lags=lag, squared=squared)
|
|
895
|
+
return float(pacf_series.loc[lag])
|
|
896
|
+
|
|
897
|
+
def pacf(
|
|
898
|
+
self: Self,
|
|
899
|
+
lags: int | list[int],
|
|
900
|
+
*,
|
|
901
|
+
squared: bool = False,
|
|
902
|
+
) -> Series[float]:
|
|
903
|
+
"""Calculate partial autocorrelation function for specified lags.
|
|
904
|
+
|
|
905
|
+
Args:
|
|
906
|
+
lags: If int, compute PACF from lag 0 to this value (inclusive).
|
|
907
|
+
If list, compute PACF at lag 0 plus each lag in the list.
|
|
908
|
+
squared: If True, compute PACF of squared returns. Defaults to False.
|
|
909
|
+
|
|
910
|
+
Returns:
|
|
911
|
+
Series of partial autocorrelations indexed by lag.
|
|
912
|
+
"""
|
|
913
|
+
if isinstance(lags, int):
|
|
914
|
+
lag_list = list(range(lags + 1))
|
|
915
|
+
else:
|
|
916
|
+
lag_list = sorted({0} | set(lags))
|
|
917
|
+
max_lag = max(lag_list) if lag_list else 0
|
|
918
|
+
acf_vals = self.acf(lags=max_lag, squared=squared)
|
|
919
|
+
acf_arr = array([acf_vals.loc[k] for k in range(max_lag + 1)])
|
|
920
|
+
pacf_values: list[float] = [1.0]
|
|
921
|
+
phi: list[list[float]] = []
|
|
922
|
+
for k in range(1, max_lag + 1):
|
|
923
|
+
if k == 1:
|
|
924
|
+
phi_kk = acf_arr[1]
|
|
925
|
+
else:
|
|
926
|
+
numer = acf_arr[k]
|
|
927
|
+
denom = 1.0
|
|
928
|
+
for j in range(k - 1):
|
|
929
|
+
numer -= phi[k - 2][j] * acf_arr[k - 1 - j]
|
|
930
|
+
denom -= phi[k - 2][j] * acf_arr[j + 1]
|
|
931
|
+
phi_kk = numer / denom
|
|
932
|
+
phi_row = [0.0] * k
|
|
933
|
+
for j in range(k - 1):
|
|
934
|
+
phi_row[j] = phi[k - 2][j] - phi_kk * phi[k - 2][k - 2 - j]
|
|
935
|
+
phi_row[k - 1] = phi_kk
|
|
936
|
+
phi.append(phi_row)
|
|
937
|
+
pacf_values.append(phi_kk)
|
|
938
|
+
result = {lag: pacf_values[lag] for lag in lag_list}
|
|
939
|
+
return Series(
|
|
940
|
+
data=[result[lag] for lag in lag_list],
|
|
941
|
+
index=lag_list,
|
|
942
|
+
name="PACF",
|
|
943
|
+
dtype="float64",
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
def ljung_box(
|
|
947
|
+
self: Self,
|
|
948
|
+
lags: int | list[int],
|
|
949
|
+
*,
|
|
950
|
+
squared: bool = False,
|
|
951
|
+
) -> tuple[float, float, list[int]]:
|
|
952
|
+
"""Compute Ljung-Box test for autocorrelation.
|
|
953
|
+
|
|
954
|
+
Args:
|
|
955
|
+
lags: If int, use lags 1 through this value. If list, use the given
|
|
956
|
+
lags (lag 0 excluded from test).
|
|
957
|
+
squared: If True, test autocorrelation of squared returns.
|
|
958
|
+
Defaults to False.
|
|
959
|
+
|
|
960
|
+
Returns:
|
|
961
|
+
Tuple of (statistic, pvalue, lags) where statistic is the Ljung-Box
|
|
962
|
+
Q statistic, pvalue is the chi-squared p-value, and lags is the
|
|
963
|
+
list of lags used.
|
|
964
|
+
"""
|
|
965
|
+
rets = self._returns_series(squared=squared)
|
|
966
|
+
n = len(rets)
|
|
967
|
+
if isinstance(lags, int):
|
|
968
|
+
lag_list = list(range(1, lags + 1))
|
|
969
|
+
else:
|
|
970
|
+
lag_list = sorted({k for k in lags if k > 0})
|
|
971
|
+
if not lag_list:
|
|
972
|
+
return 0.0, 1.0, []
|
|
973
|
+
r_k_sq_sum = 0.0
|
|
974
|
+
for k in lag_list:
|
|
975
|
+
if k < n:
|
|
976
|
+
r_k = float(rets.autocorr(lag=k))
|
|
977
|
+
r_k_sq_sum += r_k**2 / (n - k)
|
|
978
|
+
q_stat = n * (n + 2) * r_k_sq_sum
|
|
979
|
+
df = len(lag_list)
|
|
980
|
+
pval = float(1.0 - chi2.cdf(q_stat, df))
|
|
981
|
+
return q_stat, pval, lag_list
|
|
982
|
+
|
|
750
983
|
|
|
751
984
|
def timeseries_chain(
|
|
752
985
|
front: TypeOpenTimeSeries,
|
|
@@ -47,6 +47,35 @@ class _JumpParams(TypedDict, total=False):
|
|
|
47
47
|
jumps_mu: float
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _validate_ar1_coef(ar1_coef: float) -> None:
|
|
51
|
+
"""Validate ar1_coef is in (-1, 1) for stationarity."""
|
|
52
|
+
if not -1.0 < ar1_coef < 1.0:
|
|
53
|
+
msg = f"ar1_coef must be in (-1, 1) for stationarity, got {ar1_coef}"
|
|
54
|
+
raise ValueError(msg)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _apply_ar1_filter(returns: DataFrame, ar1_coef: float) -> DataFrame:
|
|
58
|
+
"""Apply AR(1) filter to returns to introduce lag-1 autocorrelation.
|
|
59
|
+
|
|
60
|
+
r_t = ar1_coef * r_{t-1} + sqrt(1 - ar1_coef**2) * innovation_t
|
|
61
|
+
Preserves mean and variance of the base process.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
returns: DataFrame of shape (number_of_sims, trading_days).
|
|
65
|
+
ar1_coef: Lag-1 autocorrelation coefficient in (-1, 1).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Filtered returns.
|
|
69
|
+
"""
|
|
70
|
+
if ar1_coef == 0.0:
|
|
71
|
+
return returns
|
|
72
|
+
arr = returns.to_numpy(copy=True)
|
|
73
|
+
scale = sqrt(1.0 - ar1_coef * ar1_coef)
|
|
74
|
+
for t in range(1, arr.shape[1]):
|
|
75
|
+
arr[:, t] = ar1_coef * arr[:, t - 1] + scale * arr[:, t]
|
|
76
|
+
return DataFrame(data=arr, dtype="float64")
|
|
77
|
+
|
|
78
|
+
|
|
50
79
|
def _random_generator(seed: int | None) -> Generator:
|
|
51
80
|
"""Make a Numpy Random Generator object.
|
|
52
81
|
|
|
@@ -183,6 +212,7 @@ class ReturnSimulation(BaseModel):
|
|
|
183
212
|
trading_days_in_year: DaysInYearType = 252,
|
|
184
213
|
seed: int | None = None,
|
|
185
214
|
randomizer: Generator | None = None,
|
|
215
|
+
ar1_coef: float = 0.0,
|
|
186
216
|
) -> ReturnSimulation:
|
|
187
217
|
"""Create a Normal distribution simulation.
|
|
188
218
|
|
|
@@ -195,22 +225,29 @@ class ReturnSimulation(BaseModel):
|
|
|
195
225
|
Defaults to 252.
|
|
196
226
|
seed: Seed for random process initiation.
|
|
197
227
|
randomizer: Random process generator.
|
|
228
|
+
ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce
|
|
229
|
+
autocorrelation. Defaults to 0.0 (i.i.d. returns).
|
|
198
230
|
|
|
199
231
|
Returns:
|
|
200
232
|
Normal distribution simulation.
|
|
201
233
|
"""
|
|
234
|
+
_validate_ar1_coef(ar1_coef)
|
|
202
235
|
if not randomizer:
|
|
203
236
|
randomizer = _random_generator(seed=seed)
|
|
204
237
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
238
|
+
returns_df = DataFrame(
|
|
239
|
+
data=randomizer.normal(
|
|
240
|
+
loc=mean_annual_return / trading_days_in_year,
|
|
241
|
+
scale=mean_annual_vol / sqrt(trading_days_in_year),
|
|
242
|
+
size=(number_of_sims, trading_days),
|
|
243
|
+
),
|
|
244
|
+
dtype="float64",
|
|
209
245
|
)
|
|
246
|
+
returns = _apply_ar1_filter(returns_df, ar1_coef)
|
|
210
247
|
|
|
211
248
|
return _create_base_simulation(
|
|
212
249
|
cls=cls,
|
|
213
|
-
returns=
|
|
250
|
+
returns=returns,
|
|
214
251
|
number_of_sims=number_of_sims,
|
|
215
252
|
trading_days=trading_days,
|
|
216
253
|
trading_days_in_year=trading_days_in_year,
|
|
@@ -229,6 +266,7 @@ class ReturnSimulation(BaseModel):
|
|
|
229
266
|
trading_days_in_year: DaysInYearType = 252,
|
|
230
267
|
seed: int | None = None,
|
|
231
268
|
randomizer: Generator | None = None,
|
|
269
|
+
ar1_coef: float = 0.0,
|
|
232
270
|
) -> ReturnSimulation:
|
|
233
271
|
"""Create a Lognormal distribution simulation.
|
|
234
272
|
|
|
@@ -241,25 +279,32 @@ class ReturnSimulation(BaseModel):
|
|
|
241
279
|
Defaults to 252.
|
|
242
280
|
seed: Seed for random process initiation.
|
|
243
281
|
randomizer: Random process generator.
|
|
282
|
+
ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce
|
|
283
|
+
autocorrelation. Defaults to 0.0 (i.i.d. returns).
|
|
244
284
|
|
|
245
285
|
Returns:
|
|
246
286
|
Lognormal distribution simulation.
|
|
247
287
|
"""
|
|
288
|
+
_validate_ar1_coef(ar1_coef)
|
|
248
289
|
if not randomizer:
|
|
249
290
|
randomizer = _random_generator(seed=seed)
|
|
250
291
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
292
|
+
returns_df = DataFrame(
|
|
293
|
+
data=(
|
|
294
|
+
randomizer.lognormal(
|
|
295
|
+
mean=mean_annual_return / trading_days_in_year,
|
|
296
|
+
sigma=mean_annual_vol / sqrt(trading_days_in_year),
|
|
297
|
+
size=(number_of_sims, trading_days),
|
|
298
|
+
)
|
|
299
|
+
- 1
|
|
300
|
+
),
|
|
301
|
+
dtype="float64",
|
|
258
302
|
)
|
|
303
|
+
returns = _apply_ar1_filter(returns_df, ar1_coef)
|
|
259
304
|
|
|
260
305
|
return _create_base_simulation(
|
|
261
306
|
cls=cls,
|
|
262
|
-
returns=
|
|
307
|
+
returns=returns,
|
|
263
308
|
number_of_sims=number_of_sims,
|
|
264
309
|
trading_days=trading_days,
|
|
265
310
|
trading_days_in_year=trading_days_in_year,
|
|
@@ -278,6 +323,7 @@ class ReturnSimulation(BaseModel):
|
|
|
278
323
|
trading_days_in_year: DaysInYearType = 252,
|
|
279
324
|
seed: int | None = None,
|
|
280
325
|
randomizer: Generator | None = None,
|
|
326
|
+
ar1_coef: float = 0.0,
|
|
281
327
|
) -> ReturnSimulation:
|
|
282
328
|
"""Create a Geometric Brownian Motion simulation.
|
|
283
329
|
|
|
@@ -290,10 +336,13 @@ class ReturnSimulation(BaseModel):
|
|
|
290
336
|
Defaults to 252.
|
|
291
337
|
seed: Seed for random process initiation.
|
|
292
338
|
randomizer: Random process generator.
|
|
339
|
+
ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce
|
|
340
|
+
autocorrelation. Defaults to 0.0 (i.i.d. returns).
|
|
293
341
|
|
|
294
342
|
Returns:
|
|
295
343
|
Geometric Brownian Motion simulation.
|
|
296
344
|
"""
|
|
345
|
+
_validate_ar1_coef(ar1_coef)
|
|
297
346
|
if not randomizer:
|
|
298
347
|
randomizer = _random_generator(seed=seed)
|
|
299
348
|
|
|
@@ -308,11 +357,12 @@ class ReturnSimulation(BaseModel):
|
|
|
308
357
|
size=(number_of_sims, trading_days),
|
|
309
358
|
)
|
|
310
359
|
|
|
311
|
-
|
|
360
|
+
returns_df = DataFrame(data=drift + wiener, dtype="float64")
|
|
361
|
+
returns = _apply_ar1_filter(returns_df, ar1_coef)
|
|
312
362
|
|
|
313
363
|
return _create_base_simulation(
|
|
314
364
|
cls=cls,
|
|
315
|
-
returns=
|
|
365
|
+
returns=returns,
|
|
316
366
|
number_of_sims=number_of_sims,
|
|
317
367
|
trading_days=trading_days,
|
|
318
368
|
trading_days_in_year=trading_days_in_year,
|
|
@@ -334,6 +384,7 @@ class ReturnSimulation(BaseModel):
|
|
|
334
384
|
trading_days_in_year: DaysInYearType = 252,
|
|
335
385
|
seed: int | None = None,
|
|
336
386
|
randomizer: Generator | None = None,
|
|
387
|
+
ar1_coef: float = 0.0,
|
|
337
388
|
) -> ReturnSimulation:
|
|
338
389
|
"""Create a Merton Jump-Diffusion model simulation.
|
|
339
390
|
|
|
@@ -350,10 +401,13 @@ class ReturnSimulation(BaseModel):
|
|
|
350
401
|
Defaults to 252.
|
|
351
402
|
seed: Seed for random process initiation.
|
|
352
403
|
randomizer: Random process generator.
|
|
404
|
+
ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce
|
|
405
|
+
autocorrelation. Defaults to 0.0 (i.i.d. returns).
|
|
353
406
|
|
|
354
407
|
Returns:
|
|
355
408
|
Merton Jump-Diffusion model simulation.
|
|
356
409
|
"""
|
|
410
|
+
_validate_ar1_coef(ar1_coef)
|
|
357
411
|
if not randomizer:
|
|
358
412
|
randomizer = _random_generator(seed=seed)
|
|
359
413
|
|
|
@@ -382,13 +436,15 @@ class ReturnSimulation(BaseModel):
|
|
|
382
436
|
- jumps_lamda * (jumps_mu + jumps_sigma**2.0)
|
|
383
437
|
) * (1.0 / trading_days_in_year)
|
|
384
438
|
|
|
385
|
-
|
|
439
|
+
raw_returns = poisson_jumps + drift + wiener
|
|
440
|
+
raw_returns[:, 0] = 0.0
|
|
386
441
|
|
|
387
|
-
|
|
442
|
+
returns_df = DataFrame(data=raw_returns, dtype="float64")
|
|
443
|
+
returns = _apply_ar1_filter(returns_df, ar1_coef)
|
|
388
444
|
|
|
389
445
|
return _create_base_simulation(
|
|
390
446
|
cls=cls,
|
|
391
|
-
returns=
|
|
447
|
+
returns=returns,
|
|
392
448
|
number_of_sims=number_of_sims,
|
|
393
449
|
trading_days=trading_days,
|
|
394
450
|
trading_days_in_year=trading_days_in_year,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "openseries"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.7"
|
|
4
4
|
description = "Tools for analyzing financial timeseries."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Martin Karrin", email = "martin.karrin@captor.se" },
|
|
@@ -50,7 +50,8 @@ dependencies = [
|
|
|
50
50
|
"python-dateutil>=2.8.2",
|
|
51
51
|
"requests>=2.20.0",
|
|
52
52
|
"scipy>=1.14.1",
|
|
53
|
-
"scikit-learn>=1.4.0"
|
|
53
|
+
"scikit-learn>=1.4.0",
|
|
54
|
+
"tzdata (>=2025.3)"
|
|
54
55
|
]
|
|
55
56
|
|
|
56
57
|
[project.urls]
|
|
@@ -67,7 +68,7 @@ pre-commit = ">=4.5.1"
|
|
|
67
68
|
pytest = ">=9.0.2"
|
|
68
69
|
pytest-cov = ">=7.0.0"
|
|
69
70
|
pytest-xdist = ">=3.8.0"
|
|
70
|
-
ruff = "0.
|
|
71
|
+
ruff = "0.15.6"
|
|
71
72
|
types-openpyxl = ">=3.1.2"
|
|
72
73
|
scipy-stubs = ">=1.14.1.0"
|
|
73
74
|
types-python-dateutil = ">=2.8.2"
|
|
@@ -80,7 +81,7 @@ sphinx-autodoc-typehints = ">=3.6.0"
|
|
|
80
81
|
sphinx-rtd-theme = ">=3.1.0rc1"
|
|
81
82
|
|
|
82
83
|
[build-system]
|
|
83
|
-
requires = ["poetry-core>=2.
|
|
84
|
+
requires = ["poetry-core>=2.3.1"]
|
|
84
85
|
build-backend = "poetry.core.masonry.api"
|
|
85
86
|
|
|
86
87
|
[tool.setuptools.package-data]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|