skfolio 0.9.1__py3-none-any.whl → 0.10.1__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.
- skfolio/distribution/multivariate/_vine_copula.py +35 -34
- skfolio/distribution/univariate/_base.py +20 -15
- skfolio/exceptions.py +5 -0
- skfolio/measures/__init__.py +2 -0
- skfolio/measures/_measures.py +392 -155
- skfolio/optimization/_base.py +21 -4
- skfolio/optimization/cluster/hierarchical/_base.py +16 -13
- skfolio/optimization/cluster/hierarchical/_herc.py +6 -6
- skfolio/optimization/cluster/hierarchical/_hrp.py +8 -6
- skfolio/optimization/convex/_base.py +238 -144
- skfolio/optimization/convex/_distributionally_robust.py +32 -20
- skfolio/optimization/convex/_maximum_diversification.py +15 -15
- skfolio/optimization/convex/_mean_risk.py +26 -24
- skfolio/optimization/convex/_risk_budgeting.py +23 -21
- skfolio/optimization/ensemble/__init__.py +2 -4
- skfolio/optimization/ensemble/_stacking.py +1 -1
- skfolio/optimization/naive/_naive.py +2 -2
- skfolio/population/_population.py +30 -9
- skfolio/portfolio/_base.py +68 -26
- skfolio/portfolio/_multi_period_portfolio.py +5 -0
- skfolio/portfolio/_portfolio.py +5 -0
- skfolio/prior/__init__.py +6 -2
- skfolio/prior/_base.py +7 -3
- skfolio/prior/_black_litterman.py +14 -12
- skfolio/prior/_empirical.py +8 -7
- skfolio/prior/_entropy_pooling.py +1493 -0
- skfolio/prior/_factor_model.py +39 -22
- skfolio/prior/_opinion_pooling.py +475 -0
- skfolio/prior/_synthetic_data.py +10 -8
- skfolio/uncertainty_set/_bootstrap.py +4 -4
- skfolio/uncertainty_set/_empirical.py +6 -6
- skfolio/utils/equations.py +10 -4
- skfolio/utils/figure.py +185 -0
- skfolio/utils/tools.py +4 -2
- {skfolio-0.9.1.dist-info → skfolio-0.10.1.dist-info}/METADATA +105 -14
- {skfolio-0.9.1.dist-info → skfolio-0.10.1.dist-info}/RECORD +40 -38
- {skfolio-0.9.1.dist-info → skfolio-0.10.1.dist-info}/WHEEL +1 -1
- skfolio/synthetic_returns/__init__.py +0 -1
- /skfolio/{optimization/ensemble/_base.py → utils/composition.py} +0 -0
- {skfolio-0.9.1.dist-info → skfolio-0.10.1.dist-info}/licenses/LICENSE +0 -0
- {skfolio-0.9.1.dist-info → skfolio-0.10.1.dist-info}/top_level.txt +0 -0
@@ -34,7 +34,6 @@ import numpy as np
|
|
34
34
|
import numpy.typing as npt
|
35
35
|
import plotly.express as px
|
36
36
|
import plotly.graph_objects as go
|
37
|
-
import scipy.stats as st
|
38
37
|
import sklearn.utils as sku
|
39
38
|
import sklearn.utils.parallel as skp
|
40
39
|
import sklearn.utils.validation as skv
|
@@ -65,6 +64,7 @@ from skfolio.distribution.univariate import (
|
|
65
64
|
StudentT,
|
66
65
|
select_univariate_dist,
|
67
66
|
)
|
67
|
+
from skfolio.utils.figure import kde_trace
|
68
68
|
from skfolio.utils.tools import input_to_array, validate_input_list
|
69
69
|
|
70
70
|
_UNIFORM_SAMPLE_EPSILON = 1e-14
|
@@ -996,6 +996,7 @@ class VineCopula(BaseMultivariateDist):
|
|
996
996
|
| None = None,
|
997
997
|
subset: list[int | str] | None = None,
|
998
998
|
n_samples: int = 500,
|
999
|
+
percentile_cutoff: float | None = None,
|
999
1000
|
title: str = "Vine Copula Marginal Distributions",
|
1000
1001
|
) -> go.Figure:
|
1001
1002
|
"""
|
@@ -1025,7 +1026,7 @@ class VineCopula(BaseMultivariateDist):
|
|
1025
1026
|
If an array-like of length `n_samples` is provided, each sample is
|
1026
1027
|
conditioned on the corresponding value in the array for that asset.
|
1027
1028
|
|
1028
|
-
|
1029
|
+
When using conditional sampling, it is recommended that the
|
1029
1030
|
assets you condition on are set as central during the vine copula
|
1030
1031
|
construction. This can be specified via the `central_assets` parameter in
|
1031
1032
|
the vine copula instantiation.
|
@@ -1041,6 +1042,12 @@ class VineCopula(BaseMultivariateDist):
|
|
1041
1042
|
rows than `n_samples`, the value is adjusted to match the number of rows in
|
1042
1043
|
`X` to ensure balanced visualization.
|
1043
1044
|
|
1045
|
+
percentile_cutoff : float, default=None
|
1046
|
+
Percentile cutoff for tail truncation (percentile), in percent.
|
1047
|
+
If a float p is provided, the distribution support is truncated at
|
1048
|
+
the p-th and (100 - p)-th percentiles.
|
1049
|
+
If None, no truncation is applied (uses full min/max of returns).
|
1050
|
+
|
1044
1051
|
title : str, default="Vine Copula Marginal Distributions"
|
1045
1052
|
The title for the plot.
|
1046
1053
|
|
@@ -1051,7 +1058,6 @@ class VineCopula(BaseMultivariateDist):
|
|
1051
1058
|
"""
|
1052
1059
|
n_assets = self.n_features_in_
|
1053
1060
|
subset = subset or list(range(n_assets))
|
1054
|
-
colors = px.colors.qualitative.Plotly
|
1055
1061
|
if X is not None:
|
1056
1062
|
X = np.asarray(X)
|
1057
1063
|
if X.ndim != 2:
|
@@ -1070,30 +1076,43 @@ class VineCopula(BaseMultivariateDist):
|
|
1070
1076
|
n_samples = X.shape[0]
|
1071
1077
|
|
1072
1078
|
samples = self.sample(n_samples=n_samples, conditioning=conditioning)
|
1079
|
+
colors = px.colors.qualitative.Plotly
|
1073
1080
|
|
1074
|
-
traces = []
|
1081
|
+
traces: list[go.Scatter] = []
|
1075
1082
|
for i, s in enumerate(subset):
|
1083
|
+
visible = True if i == 0 else "legendonly"
|
1084
|
+
color = colors[i % len(colors)]
|
1085
|
+
asset = self.feature_names_in_[s]
|
1086
|
+
|
1076
1087
|
traces.append(
|
1077
|
-
|
1088
|
+
kde_trace(
|
1078
1089
|
x=samples[:, s],
|
1079
|
-
|
1080
|
-
|
1081
|
-
name=f"{
|
1082
|
-
|
1090
|
+
sample_weight=None,
|
1091
|
+
percentile_cutoff=percentile_cutoff,
|
1092
|
+
name=f"{asset} Generated",
|
1093
|
+
line_color=color,
|
1094
|
+
fill_opacity=0.17,
|
1095
|
+
line_dash="solid",
|
1096
|
+
line_width=1,
|
1097
|
+
visible=visible,
|
1083
1098
|
)
|
1084
1099
|
)
|
1085
1100
|
|
1086
|
-
|
1087
|
-
for i, s in enumerate(subset):
|
1101
|
+
if X is not None:
|
1088
1102
|
traces.append(
|
1089
|
-
|
1103
|
+
kde_trace(
|
1090
1104
|
x=X[:, s],
|
1091
|
-
|
1092
|
-
|
1093
|
-
name=f"{
|
1094
|
-
|
1105
|
+
sample_weight=None,
|
1106
|
+
percentile_cutoff=percentile_cutoff,
|
1107
|
+
name=f"{asset} Empirical",
|
1108
|
+
line_color=color,
|
1109
|
+
fill_opacity=0.17,
|
1110
|
+
line_dash="dash",
|
1111
|
+
line_width=1.5,
|
1112
|
+
visible=visible,
|
1095
1113
|
)
|
1096
1114
|
)
|
1115
|
+
|
1097
1116
|
fig = go.Figure(data=traces)
|
1098
1117
|
fig.update_layout(
|
1099
1118
|
title=title,
|
@@ -1234,21 +1253,3 @@ def _inverse_partial_derivative(
|
|
1234
1253
|
if is_count:
|
1235
1254
|
return np.array([np.nan])
|
1236
1255
|
return edge.copula.inverse_partial_derivative(X)
|
1237
|
-
|
1238
|
-
|
1239
|
-
def _kde_trace(
|
1240
|
-
x: np.ndarray, opacity: float, color: str, name: str, visible: bool
|
1241
|
-
) -> go.Scatter:
|
1242
|
-
"""Gaussian KDE line plot."""
|
1243
|
-
kde = st.gaussian_kde(x)
|
1244
|
-
x = np.linspace(min(x), max(x), 500)
|
1245
|
-
return go.Scatter(
|
1246
|
-
x=x,
|
1247
|
-
y=kde(x),
|
1248
|
-
mode="lines",
|
1249
|
-
name=name,
|
1250
|
-
line=dict(color=color),
|
1251
|
-
fill="tozeroy",
|
1252
|
-
opacity=opacity,
|
1253
|
-
visible=visible,
|
1254
|
-
)
|
@@ -202,6 +202,23 @@ class BaseUnivariateDist(BaseDistribution, ABC):
|
|
202
202
|
x = np.linspace(lower_bound, upper_bound, 1000)
|
203
203
|
|
204
204
|
traces = []
|
205
|
+
|
206
|
+
with warnings.catch_warnings():
|
207
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
208
|
+
pdfs = np.exp(self.score_samples(x.reshape(-1, 1)))
|
209
|
+
traces.append(
|
210
|
+
go.Scatter(
|
211
|
+
x=x,
|
212
|
+
y=pdfs.flatten(),
|
213
|
+
mode="lines",
|
214
|
+
name=self.__class__.__name__,
|
215
|
+
line=dict(color="rgb(31, 119, 180)", dash="solid", width=1),
|
216
|
+
fill="tozeroy",
|
217
|
+
fillcolor="rgba(31, 119, 180, 0.17)",
|
218
|
+
opacity=1.0,
|
219
|
+
)
|
220
|
+
)
|
221
|
+
|
205
222
|
if X is not None:
|
206
223
|
with warnings.catch_warnings():
|
207
224
|
warnings.filterwarnings(
|
@@ -216,25 +233,13 @@ class BaseUnivariateDist(BaseDistribution, ABC):
|
|
216
233
|
y=y_kde,
|
217
234
|
mode="lines",
|
218
235
|
name="Empirical KDE",
|
219
|
-
line=dict(color="rgb(85,168,104)"),
|
236
|
+
line=dict(color="rgb(85, 168, 104)", dash="dash", width=2),
|
220
237
|
fill="tozeroy",
|
238
|
+
fillcolor="rgba(85, 168, 104, 0.17)",
|
239
|
+
opacity=1.0,
|
221
240
|
)
|
222
241
|
)
|
223
242
|
|
224
|
-
with warnings.catch_warnings():
|
225
|
-
warnings.filterwarnings("ignore", category=UserWarning)
|
226
|
-
pdfs = np.exp(self.score_samples(x.reshape(-1, 1)))
|
227
|
-
traces.append(
|
228
|
-
go.Scatter(
|
229
|
-
x=x,
|
230
|
-
y=pdfs.flatten(),
|
231
|
-
mode="lines",
|
232
|
-
name=self.__class__.__name__,
|
233
|
-
line=dict(color="rgb(31, 119, 180)"),
|
234
|
-
fill="tozeroy",
|
235
|
-
)
|
236
|
-
)
|
237
|
-
|
238
243
|
fig = go.Figure(data=traces)
|
239
244
|
fig.update_layout(
|
240
245
|
title=title,
|
skfolio/exceptions.py
CHANGED
@@ -13,6 +13,7 @@ __all__ = [
|
|
13
13
|
"GroupNotFoundError",
|
14
14
|
"NonPositiveVarianceError",
|
15
15
|
"OptimizationError",
|
16
|
+
"SolverError",
|
16
17
|
]
|
17
18
|
|
18
19
|
|
@@ -20,6 +21,10 @@ class OptimizationError(Exception):
|
|
20
21
|
"""Optimization Did not converge."""
|
21
22
|
|
22
23
|
|
24
|
+
class SolverError(Exception):
|
25
|
+
"""Solver error."""
|
26
|
+
|
27
|
+
|
23
28
|
class EquationToMatrixError(Exception):
|
24
29
|
"""Error while processing equations."""
|
25
30
|
|
skfolio/measures/__init__.py
CHANGED
@@ -13,6 +13,7 @@ from skfolio.measures._enums import (
|
|
13
13
|
from skfolio.measures._measures import (
|
14
14
|
average_drawdown,
|
15
15
|
cdar,
|
16
|
+
correlation,
|
16
17
|
cvar,
|
17
18
|
drawdown_at_risk,
|
18
19
|
edar,
|
@@ -49,6 +50,7 @@ __all__ = [
|
|
49
50
|
"RiskMeasure",
|
50
51
|
"average_drawdown",
|
51
52
|
"cdar",
|
53
|
+
"correlation",
|
52
54
|
"cvar",
|
53
55
|
"drawdown_at_risk",
|
54
56
|
"edar",
|