openseries 1.9.1__py3-none-any.whl → 1.9.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 +98 -65
- openseries/frame.py +161 -115
- openseries/load_plotly.py +1 -1
- openseries/owntypes.py +3 -14
- openseries/portfoliotools.py +11 -7
- openseries/report.py +9 -5
- openseries/series.py +6 -6
- openseries/simulation.py +6 -10
- {openseries-1.9.1.dist-info → openseries-1.9.3.dist-info}/METADATA +67 -65
- openseries-1.9.3.dist-info/RECORD +17 -0
- openseries-1.9.1.dist-info/RECORD +0 -17
- {openseries-1.9.1.dist-info → openseries-1.9.3.dist-info}/LICENSE.md +0 -0
- {openseries-1.9.1.dist-info → openseries-1.9.3.dist-info}/WHEEL +0 -0
openseries/_common_model.py
CHANGED
@@ -17,7 +17,7 @@ from math import ceil
|
|
17
17
|
from pathlib import Path
|
18
18
|
from secrets import choice
|
19
19
|
from string import ascii_letters
|
20
|
-
from typing import TYPE_CHECKING, Any, SupportsFloat, cast
|
20
|
+
from typing import TYPE_CHECKING, Any, Literal, SupportsFloat, cast
|
21
21
|
|
22
22
|
from numpy import float64, inf, isnan, log, maximum, sqrt
|
23
23
|
|
@@ -263,7 +263,10 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
263
263
|
|
264
264
|
"""
|
265
265
|
min_accepted_return: float = 0.0
|
266
|
-
|
266
|
+
order: Literal[2, 3] = 2
|
267
|
+
return self.lower_partial_moment_func(
|
268
|
+
min_accepted_return=min_accepted_return, order=order
|
269
|
+
)
|
267
270
|
|
268
271
|
@property
|
269
272
|
def ret_vol_ratio(self: Self) -> float | Series[float]:
|
@@ -298,6 +301,32 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
298
301
|
min_accepted_return=minimum_accepted_return,
|
299
302
|
)
|
300
303
|
|
304
|
+
@property
|
305
|
+
def kappa3_ratio(self: Self) -> float | Series[float]:
|
306
|
+
"""Kappa-3 ratio.
|
307
|
+
|
308
|
+
The Kappa-3 ratio is a generalized downside-risk ratio defined as
|
309
|
+
annualized arithmetic return divided by the cubic-root of the
|
310
|
+
lower partial moment of order 3 (with respect to a minimum acceptable
|
311
|
+
return, MAR). It penalizes larger downside outcomes more heavily than
|
312
|
+
the Sortino ratio (which uses order 2).
|
313
|
+
|
314
|
+
Returns:
|
315
|
+
-------
|
316
|
+
float | Pandas.Series[float]
|
317
|
+
Kappa-3 ratio calculation with the riskfree rate and
|
318
|
+
Minimum Acceptable Return (MAR) both set to zero.
|
319
|
+
|
320
|
+
"""
|
321
|
+
riskfree_rate: float = 0.0
|
322
|
+
minimum_accepted_return: float = 0.0
|
323
|
+
order: Literal[2, 3] = 3
|
324
|
+
return self.sortino_ratio_func(
|
325
|
+
riskfree_rate=riskfree_rate,
|
326
|
+
min_accepted_return=minimum_accepted_return,
|
327
|
+
order=order,
|
328
|
+
)
|
329
|
+
|
301
330
|
@property
|
302
331
|
def omega_ratio(self: Self) -> float | Series[float]:
|
303
332
|
"""https://en.wikipedia.org/wiki/Omega_ratio.
|
@@ -403,7 +432,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
403
432
|
|
404
433
|
wmdf = wmdf.reindex(index=[deyt.date() for deyt in dates], method=method)
|
405
434
|
wmdf.index = DatetimeIndex(wmdf.index)
|
406
|
-
result = wmdf.
|
435
|
+
result = wmdf.pct_change().min()
|
407
436
|
|
408
437
|
if self.tsdf.shape[1] == 1:
|
409
438
|
return float(result.iloc[0])
|
@@ -559,6 +588,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
559
588
|
countries: CountriesType | None = None,
|
560
589
|
markets: list[str] | str | None = None,
|
561
590
|
custom_holidays: list[str] | str | None = None,
|
591
|
+
method: LiteralPandasReindexMethod = "nearest",
|
562
592
|
) -> Self:
|
563
593
|
"""Align the index of .tsdf with local calendar business days.
|
564
594
|
|
@@ -570,6 +600,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
570
600
|
(List of) markets code(s) according to pandas-market-calendars
|
571
601
|
custom_holidays: list[str] | str, optional
|
572
602
|
Argument where missing holidays can be added
|
603
|
+
method: LiteralPandasReindexMethod, default: "nearest"
|
573
604
|
|
574
605
|
Returns:
|
575
606
|
-------
|
@@ -620,7 +651,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
620
651
|
freq=CustomBusinessDay(calendar=calendar),
|
621
652
|
)
|
622
653
|
]
|
623
|
-
self.tsdf = self.tsdf.reindex(d_range, method=
|
654
|
+
self.tsdf = self.tsdf.reindex(labels=d_range, method=method, copy=False)
|
624
655
|
|
625
656
|
return self
|
626
657
|
|
@@ -1016,7 +1047,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1016
1047
|
)
|
1017
1048
|
figure.update_layout(yaxis={"tickformat": tick_fmt})
|
1018
1049
|
|
1019
|
-
if show_last
|
1050
|
+
if show_last:
|
1020
1051
|
txt = f"Last {{:{tick_fmt}}}" if tick_fmt else "Last {}"
|
1021
1052
|
|
1022
1053
|
for item in range(self.tsdf.shape[1]):
|
@@ -1063,7 +1094,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1063
1094
|
def plot_histogram(
|
1064
1095
|
self: Self,
|
1065
1096
|
plot_type: LiteralPlotlyHistogramPlotType = "bars",
|
1066
|
-
histnorm: LiteralPlotlyHistogramHistNorm = "
|
1097
|
+
histnorm: LiteralPlotlyHistogramHistNorm = "probability",
|
1067
1098
|
barmode: LiteralPlotlyHistogramBarMode = "overlay",
|
1068
1099
|
xbins_size: float | None = None,
|
1069
1100
|
opacity: float = 0.75,
|
@@ -1167,6 +1198,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1167
1198
|
figure.add_histogram(
|
1168
1199
|
x=self.tsdf.iloc[:, item],
|
1169
1200
|
cumulative={"enabled": cumulative},
|
1201
|
+
histfunc="count",
|
1170
1202
|
histnorm=histnorm,
|
1171
1203
|
name=labels[item],
|
1172
1204
|
xbins={"size": xbins_size},
|
@@ -1273,7 +1305,6 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1273
1305
|
|
1274
1306
|
result = (
|
1275
1307
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1276
|
-
.ffill()
|
1277
1308
|
.pct_change()
|
1278
1309
|
.mean()
|
1279
1310
|
* time_factor
|
@@ -1335,7 +1366,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1335
1366
|
time_factor = how_many / fraction
|
1336
1367
|
|
1337
1368
|
data = self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1338
|
-
result = data.
|
1369
|
+
result = data.pct_change().std().mul(sqrt(time_factor))
|
1339
1370
|
|
1340
1371
|
if self.tsdf.shape[1] == 1:
|
1341
1372
|
return float(cast("SupportsFloat", result.iloc[0]))
|
@@ -1533,24 +1564,21 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1533
1564
|
if drift_adjust:
|
1534
1565
|
imp_vol = (-sqrt(time_factor) / norm.ppf(level)) * (
|
1535
1566
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1536
|
-
.ffill()
|
1537
1567
|
.pct_change()
|
1538
1568
|
.quantile(1 - level, interpolation=interpolation)
|
1539
1569
|
- self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1540
|
-
.ffill()
|
1541
1570
|
.pct_change()
|
1542
1571
|
.sum()
|
1543
1572
|
/ len(
|
1544
|
-
self.tsdf.loc[
|
1545
|
-
|
1546
|
-
.pct_change(),
|
1573
|
+
self.tsdf.loc[
|
1574
|
+
cast("int", earlier) : cast("int", later)
|
1575
|
+
].pct_change(),
|
1547
1576
|
)
|
1548
1577
|
)
|
1549
1578
|
else:
|
1550
1579
|
imp_vol = (
|
1551
1580
|
-sqrt(time_factor)
|
1552
1581
|
* self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1553
|
-
.ffill()
|
1554
1582
|
.pct_change()
|
1555
1583
|
.quantile(1 - level, interpolation=interpolation)
|
1556
1584
|
/ norm.ppf(level)
|
@@ -1616,14 +1644,12 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1616
1644
|
)
|
1617
1645
|
result = [
|
1618
1646
|
cvar_df.loc[:, x] # type: ignore[call-overload,index]
|
1619
|
-
.ffill()
|
1620
1647
|
.pct_change()
|
1621
1648
|
.sort_values()
|
1622
1649
|
.iloc[
|
1623
1650
|
: ceil(
|
1624
1651
|
(1 - level)
|
1625
1652
|
* cvar_df.loc[:, x] # type: ignore[index]
|
1626
|
-
.ffill()
|
1627
1653
|
.pct_change()
|
1628
1654
|
.count(),
|
1629
1655
|
),
|
@@ -1640,24 +1666,28 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1640
1666
|
dtype="float64",
|
1641
1667
|
)
|
1642
1668
|
|
1643
|
-
def
|
1669
|
+
def lower_partial_moment_func(
|
1644
1670
|
self: Self,
|
1645
1671
|
min_accepted_return: float = 0.0,
|
1672
|
+
order: Literal[2, 3] = 2,
|
1646
1673
|
months_from_last: int | None = None,
|
1647
1674
|
from_date: dt.date | None = None,
|
1648
1675
|
to_date: dt.date | None = None,
|
1649
1676
|
periods_in_a_year_fixed: DaysInYearType | None = None,
|
1650
1677
|
) -> float | Series[float]:
|
1651
|
-
"""Downside Deviation.
|
1678
|
+
"""Downside Deviation if order set to 2.
|
1652
1679
|
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1680
|
+
If order is set to 2 the function calculates the standard
|
1681
|
+
deviation of returns that are below a Minimum Accepted
|
1682
|
+
Return of zero. For general order p, it returns LPM_p^(1/p),
|
1683
|
+
i.e., the rooted lower partial moment of order p.
|
1656
1684
|
|
1657
1685
|
Parameters
|
1658
1686
|
----------
|
1659
1687
|
min_accepted_return : float, optional
|
1660
1688
|
The annualized Minimum Accepted Return (MAR)
|
1689
|
+
order: int, default: 2
|
1690
|
+
Order of partial moment
|
1661
1691
|
months_from_last : int, optional
|
1662
1692
|
number of months offset as positive integer. Overrides use of from_date
|
1663
1693
|
and to_date
|
@@ -1672,18 +1702,22 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1672
1702
|
Returns:
|
1673
1703
|
-------
|
1674
1704
|
float | Pandas.Series[float]
|
1675
|
-
Downside deviation
|
1705
|
+
Downside deviation if order set to 2
|
1676
1706
|
|
1677
1707
|
"""
|
1708
|
+
msg = f"'order' must be 2 or 3, got {order!r}."
|
1709
|
+
if order not in (2, 3):
|
1710
|
+
raise ValueError(msg)
|
1711
|
+
|
1678
1712
|
zero: float = 0.0
|
1679
1713
|
earlier, later = self.calc_range(
|
1680
1714
|
months_offset=months_from_last,
|
1681
1715
|
from_dt=from_date,
|
1682
1716
|
to_dt=to_date,
|
1683
1717
|
)
|
1718
|
+
|
1684
1719
|
how_many = (
|
1685
1720
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1686
|
-
.ffill()
|
1687
1721
|
.pct_change()
|
1688
1722
|
.count(numeric_only=True)
|
1689
1723
|
)
|
@@ -1697,23 +1731,26 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1697
1731
|
fraction = (later - earlier).days / 365.25
|
1698
1732
|
time_factor = how_many.div(fraction)
|
1699
1733
|
|
1700
|
-
|
1734
|
+
per_period_mar = min_accepted_return / time_factor
|
1735
|
+
diff = (
|
1701
1736
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1702
|
-
.ffill()
|
1703
1737
|
.pct_change()
|
1704
|
-
.sub(
|
1738
|
+
.sub(per_period_mar)
|
1705
1739
|
)
|
1706
1740
|
|
1707
|
-
|
1708
|
-
|
1709
|
-
)
|
1741
|
+
shortfall = (-diff).clip(lower=zero)
|
1742
|
+
base = shortfall.pow(order).sum() / how_many
|
1743
|
+
result = base.pow(1.0 / float(order))
|
1744
|
+
result *= sqrt(time_factor)
|
1745
|
+
|
1746
|
+
dd_order = 2
|
1710
1747
|
|
1711
1748
|
if self.tsdf.shape[1] == 1:
|
1712
1749
|
return float(result.iloc[0])
|
1713
1750
|
return Series(
|
1714
1751
|
data=result,
|
1715
1752
|
index=self.tsdf.columns,
|
1716
|
-
name="Downside deviation",
|
1753
|
+
name="Downside deviation" if order == dd_order else f"LPM{order}",
|
1717
1754
|
dtype="float64",
|
1718
1755
|
)
|
1719
1756
|
|
@@ -1808,7 +1845,6 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1808
1845
|
)
|
1809
1846
|
result: NDArray[float64] = skew(
|
1810
1847
|
a=self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1811
|
-
.ffill()
|
1812
1848
|
.pct_change()
|
1813
1849
|
.to_numpy(),
|
1814
1850
|
bias=True,
|
@@ -1856,11 +1892,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1856
1892
|
to_dt=to_date,
|
1857
1893
|
)
|
1858
1894
|
result: NDArray[float64] = kurtosis(
|
1859
|
-
a=(
|
1860
|
-
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1861
|
-
.ffill()
|
1862
|
-
.pct_change()
|
1863
|
-
),
|
1895
|
+
a=(self.tsdf.loc[cast("int", earlier) : cast("int", later)].pct_change()),
|
1864
1896
|
fisher=True,
|
1865
1897
|
bias=True,
|
1866
1898
|
nan_policy="omit",
|
@@ -1956,18 +1988,16 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
1956
1988
|
)
|
1957
1989
|
pos = (
|
1958
1990
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1959
|
-
.ffill()
|
1960
1991
|
.pct_change()[1:][
|
1961
|
-
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1962
|
-
|
1963
|
-
|
1992
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)].pct_change()[
|
1993
|
+
1:
|
1994
|
+
]
|
1964
1995
|
> zero
|
1965
1996
|
]
|
1966
1997
|
.count()
|
1967
1998
|
)
|
1968
1999
|
tot = (
|
1969
2000
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1970
|
-
.ffill()
|
1971
2001
|
.pct_change()
|
1972
2002
|
.count()
|
1973
2003
|
)
|
@@ -2047,18 +2077,22 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2047
2077
|
self: Self,
|
2048
2078
|
riskfree_rate: float = 0.0,
|
2049
2079
|
min_accepted_return: float = 0.0,
|
2080
|
+
order: Literal[2, 3] = 2,
|
2050
2081
|
months_from_last: int | None = None,
|
2051
2082
|
from_date: dt.date | None = None,
|
2052
2083
|
to_date: dt.date | None = None,
|
2053
2084
|
periods_in_a_year_fixed: DaysInYearType | None = None,
|
2054
2085
|
) -> float | Series[float]:
|
2055
|
-
"""Sortino Ratio.
|
2086
|
+
"""Sortino Ratio or Kappa3 Ratio.
|
2056
2087
|
|
2057
2088
|
The Sortino ratio calculated as ( return - risk free return )
|
2058
2089
|
/ downside deviation. The ratio implies that the riskfree asset has zero
|
2059
2090
|
volatility, and a minimum acceptable return of zero. The ratio is
|
2060
2091
|
calculated using the annualized arithmetic mean of returns.
|
2061
2092
|
https://www.investopedia.com/terms/s/sortinoratio.asp.
|
2093
|
+
If order is set to 3 the ratio calculated becomes Kappa3 which
|
2094
|
+
penalizes larger downside outcomes more heavily than the Sortino
|
2095
|
+
ratio (which uses order 2).
|
2062
2096
|
|
2063
2097
|
Parameters
|
2064
2098
|
----------
|
@@ -2066,6 +2100,8 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2066
2100
|
The return of the zero volatility asset
|
2067
2101
|
min_accepted_return : float, optional
|
2068
2102
|
The annualized Minimum Accepted Return (MAR)
|
2103
|
+
order: int, default: 2
|
2104
|
+
Order of partial moment
|
2069
2105
|
months_from_last : int, optional
|
2070
2106
|
number of months offset as positive integer. Overrides use of from_date
|
2071
2107
|
and to_date
|
@@ -2092,20 +2128,22 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2092
2128
|
periods_in_a_year_fixed=periods_in_a_year_fixed,
|
2093
2129
|
)
|
2094
2130
|
- riskfree_rate,
|
2095
|
-
) / self.
|
2131
|
+
) / self.lower_partial_moment_func(
|
2096
2132
|
min_accepted_return=min_accepted_return,
|
2133
|
+
order=order,
|
2097
2134
|
months_from_last=months_from_last,
|
2098
2135
|
from_date=from_date,
|
2099
2136
|
to_date=to_date,
|
2100
2137
|
periods_in_a_year_fixed=periods_in_a_year_fixed,
|
2101
2138
|
)
|
2102
2139
|
|
2140
|
+
sortino_order = 2
|
2103
2141
|
if self.tsdf.shape[1] == 1:
|
2104
2142
|
return float(cast("float64", ratio.iloc[0]))
|
2105
2143
|
return Series(
|
2106
2144
|
data=ratio,
|
2107
2145
|
index=self.tsdf.columns,
|
2108
|
-
name="Sortino ratio",
|
2146
|
+
name="Sortino ratio" if order == sortino_order else "Kappa-3 ratio",
|
2109
2147
|
dtype="float64",
|
2110
2148
|
)
|
2111
2149
|
|
@@ -2146,11 +2184,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2146
2184
|
from_dt=from_date,
|
2147
2185
|
to_dt=to_date,
|
2148
2186
|
)
|
2149
|
-
retdf = (
|
2150
|
-
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
2151
|
-
.ffill()
|
2152
|
-
.pct_change()
|
2153
|
-
)
|
2187
|
+
retdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].pct_change()
|
2154
2188
|
pos = retdf[retdf > min_accepted_return].sub(min_accepted_return).sum()
|
2155
2189
|
neg = retdf[retdf < min_accepted_return].sub(min_accepted_return).sum()
|
2156
2190
|
ratio = pos / -neg
|
@@ -2238,7 +2272,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2238
2272
|
period = "-".join([str(year), str(month).zfill(2)])
|
2239
2273
|
vrdf = self.tsdf.copy()
|
2240
2274
|
vrdf.index = DatetimeIndex(vrdf.index)
|
2241
|
-
resultdf = DataFrame(vrdf.
|
2275
|
+
resultdf = DataFrame(vrdf.pct_change())
|
2242
2276
|
result = resultdf.loc[period] + 1
|
2243
2277
|
cal_period = result.cumprod(axis="index").iloc[-1] - 1
|
2244
2278
|
if self.tsdf.shape[1] == 1:
|
@@ -2290,7 +2324,6 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2290
2324
|
)
|
2291
2325
|
result = (
|
2292
2326
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
2293
|
-
.ffill()
|
2294
2327
|
.pct_change()
|
2295
2328
|
.quantile(1 - level, interpolation=interpolation)
|
2296
2329
|
)
|
@@ -2339,7 +2372,6 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2339
2372
|
)
|
2340
2373
|
result = (
|
2341
2374
|
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
2342
|
-
.ffill()
|
2343
2375
|
.pct_change()
|
2344
2376
|
.rolling(observations, min_periods=observations)
|
2345
2377
|
.sum()
|
@@ -2386,11 +2418,9 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2386
2418
|
from_dt=from_date,
|
2387
2419
|
to_dt=to_date,
|
2388
2420
|
)
|
2389
|
-
zscframe =
|
2390
|
-
|
2391
|
-
|
2392
|
-
.pct_change()
|
2393
|
-
)
|
2421
|
+
zscframe = self.tsdf.loc[
|
2422
|
+
cast("int", earlier) : cast("int", later)
|
2423
|
+
].pct_change()
|
2394
2424
|
result = (zscframe.iloc[-1] - zscframe.mean()) / zscframe.std()
|
2395
2425
|
|
2396
2426
|
if self.tsdf.shape[1] == 1:
|
@@ -2459,7 +2489,6 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2459
2489
|
ret_label = cast("tuple[str]", self.tsdf.iloc[:, column].name)[0]
|
2460
2490
|
retseries = (
|
2461
2491
|
Series(self.tsdf.iloc[:, column])
|
2462
|
-
.ffill()
|
2463
2492
|
.pct_change()
|
2464
2493
|
.rolling(observations, min_periods=observations)
|
2465
2494
|
.sum()
|
@@ -2513,6 +2542,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2513
2542
|
column: int = 0,
|
2514
2543
|
observations: int = 21,
|
2515
2544
|
periods_in_a_year_fixed: DaysInYearType | None = None,
|
2545
|
+
dlta_degr_freedms: int = 1,
|
2516
2546
|
) -> DataFrame:
|
2517
2547
|
"""Calculate rolling annualised volatilities.
|
2518
2548
|
|
@@ -2525,6 +2555,8 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2525
2555
|
periods_in_a_year_fixed : DaysInYearType, optional
|
2526
2556
|
Allows locking the periods-in-a-year to simplify test cases and
|
2527
2557
|
comparisons
|
2558
|
+
dlta_degr_freedms: int, default: 1
|
2559
|
+
Variance bias factor taking the value 0 or 1.
|
2528
2560
|
|
2529
2561
|
Returns:
|
2530
2562
|
-------
|
@@ -2536,15 +2568,16 @@ class _CommonModel(BaseModel): # type: ignore[misc]
|
|
2536
2568
|
time_factor = float(periods_in_a_year_fixed)
|
2537
2569
|
else:
|
2538
2570
|
time_factor = self.periods_in_a_year
|
2571
|
+
|
2539
2572
|
vol_label = cast("tuple[str, ValueType]", self.tsdf.iloc[:, column].name)[0]
|
2540
|
-
|
2541
|
-
|
2542
|
-
|
2543
|
-
|
2544
|
-
)
|
2545
|
-
|
2546
|
-
)
|
2573
|
+
|
2574
|
+
s = log(self.tsdf.iloc[:, column]).diff()
|
2575
|
+
volseries = s.rolling(window=observations, min_periods=observations).std(
|
2576
|
+
ddof=dlta_degr_freedms
|
2577
|
+
) * sqrt(time_factor)
|
2578
|
+
|
2547
2579
|
voldf = volseries.dropna().to_frame()
|
2580
|
+
|
2548
2581
|
voldf.columns = MultiIndex.from_arrays(
|
2549
2582
|
[
|
2550
2583
|
[vol_label],
|