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
@@ -124,11 +124,11 @@ class EmpiricalMuUncertaintySet(BaseMuUncertaintySet):
|
|
124
124
|
# fitting estimators
|
125
125
|
self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit)
|
126
126
|
|
127
|
-
|
128
|
-
n_observations, n_assets =
|
127
|
+
return_distribution = self.prior_estimator_.return_distribution_
|
128
|
+
n_observations, n_assets = return_distribution.returns.shape
|
129
129
|
k = np.sqrt(st.chi2.ppf(q=self.confidence_level, df=n_assets))
|
130
130
|
|
131
|
-
sigma =
|
131
|
+
sigma = return_distribution.covariance / n_observations
|
132
132
|
if self.diagonal:
|
133
133
|
sigma = np.diag(np.diag(sigma))
|
134
134
|
|
@@ -239,11 +239,11 @@ class EmpiricalCovarianceUncertaintySet(BaseCovarianceUncertaintySet):
|
|
239
239
|
# fitting estimators
|
240
240
|
self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit)
|
241
241
|
|
242
|
-
|
243
|
-
n_observations, n_assets =
|
242
|
+
return_distribution = self.prior_estimator_.return_distribution_
|
243
|
+
n_observations, n_assets = return_distribution.returns.shape
|
244
244
|
k = np.sqrt(st.chi2.ppf(q=self.confidence_level, df=n_assets**2))
|
245
245
|
|
246
|
-
sigma =
|
246
|
+
sigma = return_distribution.covariance / n_observations
|
247
247
|
if self.diagonal:
|
248
248
|
sigma = np.diag(np.diag(sigma))
|
249
249
|
|
skfolio/utils/equations.py
CHANGED
@@ -103,6 +103,8 @@ def equations_to_matrix(
|
|
103
103
|
groups = _validate_groups(groups, name=names[0])
|
104
104
|
equations = _validate_equations(equations, name=names[1])
|
105
105
|
|
106
|
+
n_groups, n_assets = groups.shape
|
107
|
+
|
106
108
|
a_equality = []
|
107
109
|
b_equality = []
|
108
110
|
|
@@ -127,10 +129,14 @@ def equations_to_matrix(
|
|
127
129
|
raise
|
128
130
|
warnings.warn(str(e), stacklevel=2)
|
129
131
|
return (
|
130
|
-
np.array(a_equality)
|
131
|
-
|
132
|
-
np.
|
133
|
-
np.array(
|
132
|
+
np.array(a_equality, dtype=float)
|
133
|
+
if a_equality
|
134
|
+
else np.empty((0, n_assets), dtype=float),
|
135
|
+
np.array(b_equality, dtype=float),
|
136
|
+
np.array(a_inequality, dtype=float)
|
137
|
+
if a_inequality
|
138
|
+
else np.empty((0, n_assets), dtype=float),
|
139
|
+
np.array(b_inequality, dtype=float),
|
134
140
|
)
|
135
141
|
|
136
142
|
|
skfolio/utils/figure.py
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
"""Figure module."""
|
2
|
+
|
3
|
+
# Copyright (c) 2023
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
import pandas as pd
|
9
|
+
import plotly.express as px
|
10
|
+
import plotly.graph_objects as go
|
11
|
+
import scipy.stats as st
|
12
|
+
|
13
|
+
|
14
|
+
def plot_kde_distributions(
|
15
|
+
X: pd.DataFrame,
|
16
|
+
sample_weight: np.ndarray | None = None,
|
17
|
+
percentile_cutoff: float | None = None,
|
18
|
+
title: str = "Distribution of Asset Returns",
|
19
|
+
unweighted_suffix: str = "",
|
20
|
+
weighted_suffix: str = "with Sample Weight",
|
21
|
+
) -> go.Figure:
|
22
|
+
"""
|
23
|
+
Plot the Kernel Density Estimate (KDE) of return distributions for multiple assets.
|
24
|
+
|
25
|
+
Parameters
|
26
|
+
----------
|
27
|
+
X : DataFrame of shape (n_observations, n_assets)
|
28
|
+
Return data where each column corresponds to an asset and each row to an
|
29
|
+
observation.
|
30
|
+
|
31
|
+
sample_weight : ndarray of shape (n_observations,), optional
|
32
|
+
Weights to apply to each observation when computing the KDE.
|
33
|
+
If None, equal weighting is used.
|
34
|
+
|
35
|
+
percentile_cutoff : float, default=None
|
36
|
+
Percentile cutoff for tail truncation (percentile), in percent.
|
37
|
+
- If a float p is provided, the distribution support is truncated at
|
38
|
+
the p-th and (100 - p)-th percentiles.
|
39
|
+
- If None, no truncation is applied (uses full min/max of returns).
|
40
|
+
|
41
|
+
title : str, default="Distribution of Asset Returns"
|
42
|
+
Title for the Plotly figure.
|
43
|
+
|
44
|
+
unweighted_suffix : str, default=""
|
45
|
+
Suffix to append to asset names for unweighted KDE traces.
|
46
|
+
|
47
|
+
weighted_suffix : str, default="weighted"
|
48
|
+
Suffix to append to asset names for weighted KDE traces.
|
49
|
+
|
50
|
+
Returns
|
51
|
+
-------
|
52
|
+
fig : plotly.graph_objects.Figure
|
53
|
+
A Plotly Figure object containing overlaid KDE plots for each asset,
|
54
|
+
with separate traces for weighted and unweighted distributions if weights
|
55
|
+
are provided.
|
56
|
+
"""
|
57
|
+
asset_names = X.columns.tolist()
|
58
|
+
X = X.values
|
59
|
+
colors = px.colors.qualitative.Plotly
|
60
|
+
|
61
|
+
traces: list[go.Scatter] = []
|
62
|
+
|
63
|
+
for i, asset in enumerate(asset_names):
|
64
|
+
x = X[:, i]
|
65
|
+
color = colors[i % len(colors)]
|
66
|
+
visible = True if i == 0 else "legendonly"
|
67
|
+
|
68
|
+
# Unweighted: solid line
|
69
|
+
traces.append(
|
70
|
+
kde_trace(
|
71
|
+
x=x,
|
72
|
+
sample_weight=None,
|
73
|
+
percentile_cutoff=percentile_cutoff,
|
74
|
+
name=f"{asset} {unweighted_suffix}".strip(),
|
75
|
+
line_color=color,
|
76
|
+
fill_opacity=0.17,
|
77
|
+
line_dash="solid",
|
78
|
+
line_width=1,
|
79
|
+
visible=visible,
|
80
|
+
)
|
81
|
+
)
|
82
|
+
|
83
|
+
# Weighted: dashed, thicker line
|
84
|
+
if sample_weight is not None:
|
85
|
+
traces.append(
|
86
|
+
kde_trace(
|
87
|
+
x=x,
|
88
|
+
sample_weight=sample_weight,
|
89
|
+
percentile_cutoff=percentile_cutoff,
|
90
|
+
name=f"{asset} {weighted_suffix}".strip(),
|
91
|
+
line_color=color,
|
92
|
+
fill_opacity=0.17,
|
93
|
+
line_dash="dash",
|
94
|
+
line_width=1.5,
|
95
|
+
visible=visible,
|
96
|
+
)
|
97
|
+
)
|
98
|
+
|
99
|
+
fig = go.Figure(data=traces)
|
100
|
+
fig.update_layout(
|
101
|
+
title=title,
|
102
|
+
xaxis_title="Returns",
|
103
|
+
yaxis_title="Probability Density",
|
104
|
+
)
|
105
|
+
fig.update_xaxes(tickformat=".0%")
|
106
|
+
return fig
|
107
|
+
|
108
|
+
|
109
|
+
def kde_trace(
|
110
|
+
x: np.ndarray,
|
111
|
+
sample_weight: np.ndarray | None,
|
112
|
+
percentile_cutoff: float | None,
|
113
|
+
name: str,
|
114
|
+
line_color: str,
|
115
|
+
fill_opacity: float,
|
116
|
+
line_dash: str,
|
117
|
+
line_width: float,
|
118
|
+
visible: bool,
|
119
|
+
) -> go.Scatter:
|
120
|
+
"""
|
121
|
+
Create a Plotly Scatter trace representing a Gaussian kernel density estimate (KDE),
|
122
|
+
with customizable line style and fill opacity.
|
123
|
+
|
124
|
+
Parameters
|
125
|
+
----------
|
126
|
+
x : ndarray of shape (n_observations,)
|
127
|
+
One-dimensional array of sample values for which the KDE is computed.
|
128
|
+
|
129
|
+
sample_weight : ndarray of shape (n_observations,), optional
|
130
|
+
Weights to apply to each observation when computing the KDE.
|
131
|
+
If None, equal weighting is used.
|
132
|
+
|
133
|
+
percentile_cutoff : float, default=None
|
134
|
+
Percentile cutoff for tail truncation (percentile), in percent.
|
135
|
+
- If a float p is provided, the distribution support is truncated at
|
136
|
+
the p-th and (100 - p)-th percentiles.
|
137
|
+
- If None, no truncation is applied (uses full min/max of returns).
|
138
|
+
|
139
|
+
name : str
|
140
|
+
Legend name for this trace.
|
141
|
+
|
142
|
+
line_color : str
|
143
|
+
Color of the KDE line (hex or named CSS color).
|
144
|
+
|
145
|
+
fill_opacity : float
|
146
|
+
Opacity of the filled area under the curve (0 to 1).
|
147
|
+
|
148
|
+
line_dash : str
|
149
|
+
Dash style for the line ("solid", "dash", "dot", etc.).
|
150
|
+
|
151
|
+
line_width : float
|
152
|
+
Width of the line.
|
153
|
+
|
154
|
+
visible : bool
|
155
|
+
Initial visibility of the trace in the Plotly figure.
|
156
|
+
|
157
|
+
Returns
|
158
|
+
-------
|
159
|
+
go.Scatter
|
160
|
+
A Plotly Scatter trace with the KDE line and shaded area under the curve.
|
161
|
+
"""
|
162
|
+
if percentile_cutoff is None:
|
163
|
+
lower, upper = x.min(), x.max()
|
164
|
+
else:
|
165
|
+
lower = np.percentile(x, percentile_cutoff)
|
166
|
+
upper = np.percentile(x, 100.0 - percentile_cutoff)
|
167
|
+
|
168
|
+
xs = np.linspace(lower, upper, 500)
|
169
|
+
ys = st.gaussian_kde(x, weights=sample_weight)(xs)
|
170
|
+
|
171
|
+
# build RGBA fill color from the line_color hex
|
172
|
+
r, g, b = tuple(int(line_color.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4))
|
173
|
+
fill_color = f"rgba({r},{g},{b},{fill_opacity})"
|
174
|
+
|
175
|
+
return go.Scatter(
|
176
|
+
x=xs,
|
177
|
+
y=ys,
|
178
|
+
mode="lines",
|
179
|
+
name=name,
|
180
|
+
visible=visible,
|
181
|
+
line=dict(color=line_color, dash=line_dash, width=line_width),
|
182
|
+
fill="tozeroy",
|
183
|
+
fillcolor=fill_color,
|
184
|
+
opacity=1.0,
|
185
|
+
)
|
skfolio/utils/tools.py
CHANGED
@@ -326,7 +326,9 @@ def args_names(func: object) -> list[str]:
|
|
326
326
|
|
327
327
|
|
328
328
|
def check_estimator(
|
329
|
-
estimator: skb.BaseEstimator | None,
|
329
|
+
estimator: skb.BaseEstimator | None,
|
330
|
+
default: skb.BaseEstimator | None,
|
331
|
+
check_type: Any,
|
330
332
|
):
|
331
333
|
"""Check the estimator type and returns its cloned version it provided, otherwise
|
332
334
|
return the default estimator.
|
@@ -336,7 +338,7 @@ def check_estimator(
|
|
336
338
|
estimator : BaseEstimator, optional
|
337
339
|
Estimator.
|
338
340
|
|
339
|
-
default : BaseEstimator
|
341
|
+
default : BaseEstimator, optional
|
340
342
|
Default estimator to return when `estimator` is `None`.
|
341
343
|
|
342
344
|
check_type : Any
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: skfolio
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.10.1
|
4
4
|
Summary: Portfolio optimization built on top of scikit-learn
|
5
5
|
Author-email: Hugo Delatte <delatte.hugo@gmail.com>
|
6
6
|
Maintainer-email: Hugo Delatte <delatte.hugo@gmail.com>, Matteo Manzi <matteomanzi09@gmail.com>
|
@@ -92,7 +92,7 @@ Dynamic: license-file
|
|
92
92
|
|
93
93
|
.. -*- mode: rst -*-
|
94
94
|
|
95
|
-
|Licence| |Codecov| |Black| |PythonVersion| |PyPi| |CI/CD| |Downloads| |Ruff| |Contribution| |Website| |JupyterLite|
|
95
|
+
|Licence| |Codecov| |Black| |PythonVersion| |PyPi| |CI/CD| |Downloads| |Ruff| |Contribution| |Website| |JupyterLite| |Discord|
|
96
96
|
|
97
97
|
.. |Licence| image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
|
98
98
|
:target: https://github.com/skfolio/skfolio/blob/main/LICENSE
|
@@ -127,6 +127,9 @@ Dynamic: license-file
|
|
127
127
|
.. |JupyterLite| image:: https://jupyterlite.rtfd.io/en/latest/_static/badge.svg
|
128
128
|
:target: https://skfolio.org/lite
|
129
129
|
|
130
|
+
.. |Discord| image:: https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white
|
131
|
+
:target: https://discord.gg/Bu7EtNYugS
|
132
|
+
|
130
133
|
.. |PythonMinVersion| replace:: 3.10
|
131
134
|
.. |NumpyMinVersion| replace:: 1.23.4
|
132
135
|
.. |ScipyMinVersion| replace:: 1.8.0
|
@@ -281,6 +284,8 @@ Available models
|
|
281
284
|
* Black & Litterman
|
282
285
|
* Factor Model
|
283
286
|
* Synthetic Data (Stress Test, Factor Stress Test)
|
287
|
+
* Entropy Pooling
|
288
|
+
* Opinion Pooling
|
284
289
|
|
285
290
|
* Uncertainty Set Estimator:
|
286
291
|
* On Expected Returns:
|
@@ -296,6 +301,7 @@ Available models
|
|
296
301
|
* Drop Highly Correlated Assets
|
297
302
|
* Select Non-Expiring Assets
|
298
303
|
* Select Complete Assets (handle late inception, delisting, etc.)
|
304
|
+
* Drop Zero Variance
|
299
305
|
|
300
306
|
* Cross-Validation and Model Selection:
|
301
307
|
* Compatible with all `sklearn` methods (KFold, etc.)
|
@@ -379,13 +385,21 @@ Imports
|
|
379
385
|
)
|
380
386
|
from skfolio.optimization import (
|
381
387
|
MeanRisk,
|
388
|
+
HierarchicalRiskParity,
|
382
389
|
NestedClustersOptimization,
|
383
390
|
ObjectiveFunction,
|
384
391
|
RiskBudgeting,
|
385
392
|
)
|
386
393
|
from skfolio.pre_selection import SelectKExtremes
|
387
394
|
from skfolio.preprocessing import prices_to_returns
|
388
|
-
from skfolio.prior import
|
395
|
+
from skfolio.prior import (
|
396
|
+
BlackLitterman,
|
397
|
+
EmpiricalPrior,
|
398
|
+
EntropyPooling,
|
399
|
+
FactorModel,
|
400
|
+
OpinionPooling,
|
401
|
+
SyntheticData,
|
402
|
+
)
|
389
403
|
from skfolio.uncertainty_set import BootstrapMuUncertaintySet
|
390
404
|
|
391
405
|
Load Dataset
|
@@ -571,11 +585,13 @@ Factor Model
|
|
571
585
|
|
572
586
|
factor_prices = load_factors_dataset()
|
573
587
|
|
574
|
-
X,
|
575
|
-
X_train, X_test,
|
588
|
+
X, factors = prices_to_returns(prices, factor_prices)
|
589
|
+
X_train, X_test, factors_train, factors_test = train_test_split(
|
590
|
+
X, factors, test_size=0.33, shuffle=False
|
591
|
+
)
|
576
592
|
|
577
593
|
model = MeanRisk(prior_estimator=FactorModel())
|
578
|
-
model.fit(X_train,
|
594
|
+
model.fit(X_train, factors_train)
|
579
595
|
|
580
596
|
print(model.weights_)
|
581
597
|
|
@@ -584,7 +600,6 @@ Factor Model
|
|
584
600
|
print(portfolio.calmar_ratio)
|
585
601
|
print(portfolio.summary())
|
586
602
|
|
587
|
-
|
588
603
|
Factor Model & Covariance Detoning
|
589
604
|
----------------------------------
|
590
605
|
.. code-block:: python
|
@@ -645,7 +660,7 @@ Combinatorial Purged Cross-Validation
|
|
645
660
|
|
646
661
|
cv = CombinatorialPurgedCV(n_folds=10, n_test_folds=2)
|
647
662
|
|
648
|
-
print(cv.
|
663
|
+
print(cv.summary(X_train))
|
649
664
|
|
650
665
|
population = cross_val_predict(model, X_train, cv=cv)
|
651
666
|
|
@@ -661,7 +676,7 @@ Minimum CVaR Optimization on Synthetic Returns
|
|
661
676
|
.. code-block:: python
|
662
677
|
|
663
678
|
vine = VineCopula(log_transform=True, n_jobs=-1)
|
664
|
-
prior =
|
679
|
+
prior = SyntheticData(distribution_estimator=vine, n_samples=2000)
|
665
680
|
model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=prior)
|
666
681
|
model.fit(X)
|
667
682
|
print(model.weights_)
|
@@ -671,7 +686,7 @@ Stress Test
|
|
671
686
|
-----------
|
672
687
|
.. code-block:: python
|
673
688
|
|
674
|
-
vine = VineCopula(log_transform=True, central_assets=["BAC"]
|
689
|
+
vine = VineCopula(log_transform=True, central_assets=["BAC"], n_jobs=-1)
|
675
690
|
vine.fit(X)
|
676
691
|
X_stressed = vine.sample(n_samples=10_000, conditioning = {"BAC": -0.2})
|
677
692
|
ptf_stressed = model.predict(X_stressed)
|
@@ -689,7 +704,7 @@ Minimum CVaR Optimization on Synthetic Factors
|
|
689
704
|
)
|
690
705
|
factor_model = FactorModel(factor_prior_estimator=factor_prior)
|
691
706
|
model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=factor_model)
|
692
|
-
model.fit(X,
|
707
|
+
model.fit(X, factors)
|
693
708
|
print(model.weights_)
|
694
709
|
|
695
710
|
|
@@ -700,9 +715,85 @@ Factor Stress Test
|
|
700
715
|
factor_model.set_params(factor_prior_estimator__sample_args=dict(
|
701
716
|
conditioning={"QUAL": -0.5}
|
702
717
|
))
|
703
|
-
factor_model.fit(X,
|
704
|
-
|
705
|
-
stressed_ptf = model.predict(
|
718
|
+
factor_model.fit(X, factors)
|
719
|
+
stressed_dist = factor_model.return_distribution_
|
720
|
+
stressed_ptf = model.predict(stressed_dist)
|
721
|
+
|
722
|
+
Entropy Pooling
|
723
|
+
---------------
|
724
|
+
.. code-block:: python
|
725
|
+
|
726
|
+
entropy_pooling = EntropyPooling(
|
727
|
+
mean_views=[
|
728
|
+
"JPM == -0.002",
|
729
|
+
"PG >= LLY",
|
730
|
+
"BAC >= prior(BAC) * 1.2",
|
731
|
+
],
|
732
|
+
cvar_views=[
|
733
|
+
"GE == 0.08",
|
734
|
+
],
|
735
|
+
)
|
736
|
+
entropy_pooling.fit(X)
|
737
|
+
print(entropy_pooling.relative_entropy_)
|
738
|
+
print(entropy_pooling.effective_number_of_scenarios_)
|
739
|
+
print(entropy_pooling.return_distribution_.sample_weight)
|
740
|
+
|
741
|
+
CVaR Hierarchical Risk Parity optimization on Entropy Pooling
|
742
|
+
-------------------------------------------------------------
|
743
|
+
.. code-block:: python
|
744
|
+
|
745
|
+
entropy_pooling = EntropyPooling(cvar_views=["GE == 0.08"])
|
746
|
+
model = HierarchicalRiskParity(
|
747
|
+
risk_measure=RiskMeasure.CVAR,
|
748
|
+
prior_estimator=entropy_pooling
|
749
|
+
)
|
750
|
+
model.fit(X)
|
751
|
+
print(model.weights_)
|
752
|
+
|
753
|
+
Stress Test with Entropy Pooling on Factor Synthetic Data
|
754
|
+
---------------------------------------------------------
|
755
|
+
.. code-block:: python
|
756
|
+
|
757
|
+
# Regular Vine Copula and sampling of 100,000 synthetic factor returns
|
758
|
+
factor_synth = SyntheticData(
|
759
|
+
n_samples=100_000,
|
760
|
+
distribution_estimator=VineCopula(log_transform=True, n_jobs=-1, random_state=0)
|
761
|
+
)
|
762
|
+
|
763
|
+
# Entropy Pooling by imposing a CVaR-95% of 10% on the Quality factor
|
764
|
+
factor_entropy_pooling = EntropyPooling(
|
765
|
+
prior_estimator=factor_synth,
|
766
|
+
cvar_views=["QUAL == 0.10"],
|
767
|
+
)
|
768
|
+
|
769
|
+
factor_entropy_pooling.fit(X, factors)
|
770
|
+
|
771
|
+
# We retrieve the stressed distribution:
|
772
|
+
stressed_dist = factor_model.return_distribution_
|
773
|
+
|
774
|
+
# We stress-test our portfolio:
|
775
|
+
stressed_ptf = model.predict(stressed_dist)
|
776
|
+
|
777
|
+
Opinion Pooling
|
778
|
+
---------------
|
779
|
+
.. code-block:: python
|
780
|
+
|
781
|
+
# We consider two expert opinions, each generated via Entropy Pooling with
|
782
|
+
# user-defined views.
|
783
|
+
# We assign probabilities of 40% to Expert 1, 50% to Expert 2, and by default
|
784
|
+
# the remaining 10% is allocated to the prior distribution:
|
785
|
+
opinion_1 = EntropyPooling(cvar_views=["AMD == 0.10"])
|
786
|
+
opinion_2 = EntropyPooling(
|
787
|
+
mean_views=["AMD >= BAC", "JPM <= prior(JPM) * 0.8"],
|
788
|
+
cvar_views=["GE == 0.12"],
|
789
|
+
)
|
790
|
+
|
791
|
+
opinion_pooling = OpinionPooling(
|
792
|
+
estimators=[("opinion_1", opinion_1), ("opinion_2", opinion_2)],
|
793
|
+
opinion_probabilities=[0.4, 0.5],
|
794
|
+
)
|
795
|
+
|
796
|
+
opinion_pooling.fit(X)
|
706
797
|
|
707
798
|
|
708
799
|
Recognition
|
@@ -1,5 +1,5 @@
|
|
1
1
|
skfolio/__init__.py,sha256=XdSV1bcfft5pNl5Y_mX8MR0IzjXjRs8uRURp42UGa08,635
|
2
|
-
skfolio/exceptions.py,sha256=
|
2
|
+
skfolio/exceptions.py,sha256=4k4cFgV0qCAovDKe0XqthzKs9SX871BTv1GKfX00ovc,880
|
3
3
|
skfolio/typing.py,sha256=5wnu_qoGZtCWKu-nHlZ5w3rOKy5CXxGI5ZvzDSR9pLU,1394
|
4
4
|
skfolio/cluster/__init__.py,sha256=ycySaq2MgG3etqNF-pITuYKfYPHYm3-frjFc8PRzMc0,267
|
5
5
|
skfolio/cluster/_hierarchical.py,sha256=PTtr6H4keY6DEVvXyYM24AnNjj72sNaXKjGFEyMXZ5c,12839
|
@@ -27,17 +27,17 @@ skfolio/distribution/copula/_utils.py,sha256=drMtv71bkwlerR0HJCdNCZTuFSitN5vn33a
|
|
27
27
|
skfolio/distribution/multivariate/__init__.py,sha256=E9AR0Hh5wWShOTwj62R1RVMkzZpXc5Ams4ppibwhrUY,339
|
28
28
|
skfolio/distribution/multivariate/_base.py,sha256=MV3rhTafPlKdb3wuLbHfhApyV1ll7WmfzdR97Dq8VZw,8716
|
29
29
|
skfolio/distribution/multivariate/_utils.py,sha256=WNL1lzO0Ki5x_yO8p3GRKrXwG4fK99je7sDQ3avyUQ8,19274
|
30
|
-
skfolio/distribution/multivariate/_vine_copula.py,sha256=
|
30
|
+
skfolio/distribution/multivariate/_vine_copula.py,sha256=2SPUwDthWe8pcDpXMZ6dqLMjpI0HGdkguP0sziOlEYc,50099
|
31
31
|
skfolio/distribution/univariate/__init__.py,sha256=m9vZUhZyRUT5IOQRixGPdGci1wtC5ua8RWtHsC8HAlU,628
|
32
|
-
skfolio/distribution/univariate/_base.py,sha256=
|
32
|
+
skfolio/distribution/univariate/_base.py,sha256=8uleYSiCJ4n6X1uI0ju5f4l0iLvJcyS3Gncxh7yyH90,10096
|
33
33
|
skfolio/distribution/univariate/_gaussian.py,sha256=pe8YxTQjvObeVeZD2YXduN5M-k2kNNTy2q0AvYCm1n4,4274
|
34
34
|
skfolio/distribution/univariate/_johnson_su.py,sha256=Dl1WyCmn-sRE4BrckVNGXHz9biDQtXyPq1JXEPKIHBo,4857
|
35
35
|
skfolio/distribution/univariate/_normal_inverse_gaussian.py,sha256=oq5omNUQanFWBGaYSNwf9YDa6c-B1j9ZErq6p96resc,4983
|
36
36
|
skfolio/distribution/univariate/_selection.py,sha256=6KL4gngiLKwaBUpCDX19ABOkMBzZp1YVRnXFrUtppCs,3110
|
37
37
|
skfolio/distribution/univariate/_student_t.py,sha256=GcI4fKp6q5XegfvT_i3AqfWlUMxCq7A5sX6Xsf4pye8,4553
|
38
|
-
skfolio/measures/__init__.py,sha256=
|
38
|
+
skfolio/measures/__init__.py,sha256=saJ3tGQyZ5MRtwnj0tVS9L4yvnLJVSdyXVyH5_FyoNo,1683
|
39
39
|
skfolio/measures/_enums.py,sha256=S6WOT8NHzm-eMHELuOjngIBupCctCdiTA2BaJlWl-4E,8956
|
40
|
-
skfolio/measures/_measures.py,sha256
|
40
|
+
skfolio/measures/_measures.py,sha256=-pF9DtJqsFliSiv9Ek_7IzytTmoKQFdqCxUl5fdSlYQ,28790
|
41
41
|
skfolio/metrics/__init__.py,sha256=ebu5h7Q9X0f3ZZ1VFmAEBPic2sirboKG_zNBHO5abjo,98
|
42
42
|
skfolio/metrics/_scorer.py,sha256=L-qct4cby15a4xC4arSaG5__1mxBCQYeMjlrHBIVnSY,4325
|
43
43
|
skfolio/model_selection/__init__.py,sha256=BT8VCXW7C4bXI2Oam4amTHOcJVlKxLpkcsHjB63pZHQ,524
|
@@ -64,30 +64,29 @@ skfolio/moments/expected_returns/_equilibrium_mu.py,sha256=A4zAYZ7ex2Y68YV0HajYD
|
|
64
64
|
skfolio/moments/expected_returns/_ew_mu.py,sha256=vDOqpTpTY3iaJc9PfMU_dpdfglT1dJ_DuM3pCTpjHpc,2125
|
65
65
|
skfolio/moments/expected_returns/_shrunk_mu.py,sha256=nqypZJweZIf6u3Idz-TLPHiD3h3XzuKgTEQWJHSVnwo,8292
|
66
66
|
skfolio/optimization/__init__.py,sha256=LA4n85e-wVTeRNI-NlTU1ID5FhP3-B410kmsh9268Ho,1049
|
67
|
-
skfolio/optimization/_base.py,sha256=
|
67
|
+
skfolio/optimization/_base.py,sha256=tbD-HiulBXHMAbrMGq8K62qUxVHu6uf1Cl0pT34VyIc,6351
|
68
68
|
skfolio/optimization/cluster/__init__.py,sha256=nxsuDxviDbj-YMHhQXIkUEWUoKPhPn10bQ0_nULNUoE,424
|
69
69
|
skfolio/optimization/cluster/_nco.py,sha256=Gbd18HYlwq_MUd9JmytM1-Uqu-GFT8NXb8QWPVgmDxk,16433
|
70
70
|
skfolio/optimization/cluster/hierarchical/__init__.py,sha256=eT1A6YKETKCBEnrUc6pHwyTkDVRcUr8jtdtmN3kdh0c,446
|
71
|
-
skfolio/optimization/cluster/hierarchical/_base.py,sha256=
|
72
|
-
skfolio/optimization/cluster/hierarchical/_herc.py,sha256=
|
73
|
-
skfolio/optimization/cluster/hierarchical/_hrp.py,sha256=
|
71
|
+
skfolio/optimization/cluster/hierarchical/_base.py,sha256=2YH9m_L_NFdBfhPso-WVCx-Meiy_bCNYVukt72nZHno,16480
|
72
|
+
skfolio/optimization/cluster/hierarchical/_herc.py,sha256=3Mwfe6PGWzWWWpOYnfLTWAmeNcYE1d3eh1uXB9hqAyM,20476
|
73
|
+
skfolio/optimization/cluster/hierarchical/_hrp.py,sha256=pNr2e2COZIi9jj5IEddT4a79BCD0VjwbPeXHRRSLdoI,18287
|
74
74
|
skfolio/optimization/convex/__init__.py,sha256=q1Q2p7HcnmbQlBIA0SXm0TwDGxl7hRc0JhF1o01lFSg,605
|
75
|
-
skfolio/optimization/convex/_base.py,sha256=
|
76
|
-
skfolio/optimization/convex/_distributionally_robust.py,sha256=
|
77
|
-
skfolio/optimization/convex/_maximum_diversification.py,sha256=
|
78
|
-
skfolio/optimization/convex/_mean_risk.py,sha256=
|
79
|
-
skfolio/optimization/convex/_risk_budgeting.py,sha256=
|
80
|
-
skfolio/optimization/ensemble/__init__.py,sha256=
|
81
|
-
skfolio/optimization/ensemble/
|
82
|
-
skfolio/optimization/ensemble/_stacking.py,sha256=DaswFVBTghP10vHGESn6aLT7C9wgp-D8NuXGtpdZcwE,14192
|
75
|
+
skfolio/optimization/convex/_base.py,sha256=gIFIF5DCIDPL8FeXaipdZBbkiUHP55MsWfyc8DVQ6ng,92190
|
76
|
+
skfolio/optimization/convex/_distributionally_robust.py,sha256=BKMp718PqG02t2qQ8ejgCSuY18ZNA_4y05RxLV16OJs,18332
|
77
|
+
skfolio/optimization/convex/_maximum_diversification.py,sha256=QBHPRsAHjR4zpxJvvaKoKI0dMMTTHMlRRquB_sOWNuU,19569
|
78
|
+
skfolio/optimization/convex/_mean_risk.py,sha256=ZO2CXMeb9uOiOQVunuUmJcgsWj-uwaPw_PqZNmOV-Mk,50287
|
79
|
+
skfolio/optimization/convex/_risk_budgeting.py,sha256=jZtQXfXF1iIVTIiV9fphW4xam36qKylHmdfIgJtdG5o,23803
|
80
|
+
skfolio/optimization/ensemble/__init__.py,sha256=8YKBsrAMjyAaUWRbDG_ciNuyj0mtSPmn85ms8Afvm90,219
|
81
|
+
skfolio/optimization/ensemble/_stacking.py,sha256=i7tAHjzydb75o2xdgzV2lkqpkhEqpwsugXJjCFyV8rY,14182
|
83
82
|
skfolio/optimization/naive/__init__.py,sha256=1QKgOuA6DoqKVOsJxWKogaGPyOir6ln-aQ28PTAbtJs,181
|
84
|
-
skfolio/optimization/naive/_naive.py,sha256=
|
83
|
+
skfolio/optimization/naive/_naive.py,sha256=TRwQO7stD8hVg5OEe_QVpBgdcLaT_jUZco2lf5skYqo,6469
|
85
84
|
skfolio/population/__init__.py,sha256=ehKwWhDJCifjhEL-QezVR0xYjzRTeyHbrEMbfWjF9cU,106
|
86
|
-
skfolio/population/_population.py,sha256=
|
85
|
+
skfolio/population/_population.py,sha256=0mXy1yXNCZWeaYUu9tzRifv-NSttcy_nSlh-44PJiz4,32048
|
87
86
|
skfolio/portfolio/__init__.py,sha256=YeDSH0ZdyE-lcbDqpNw9IOltURtoM-ewAzzcec44Q5Q,586
|
88
|
-
skfolio/portfolio/_base.py,sha256=
|
89
|
-
skfolio/portfolio/_multi_period_portfolio.py,sha256=
|
90
|
-
skfolio/portfolio/_portfolio.py,sha256=
|
87
|
+
skfolio/portfolio/_base.py,sha256=1fzDIYpgz8GIDDySfAO32RN-2IX6waT6vZS6CzvkLT8,42239
|
88
|
+
skfolio/portfolio/_multi_period_portfolio.py,sha256=EI0LeJ3DokDUnumNv_Q1-P3wykjeBHpw2qa2KTi1a-M,24649
|
89
|
+
skfolio/portfolio/_portfolio.py,sha256=so9wer_KNnJqudvalNeV9ubEy5WIumE-PxvEAAL9Ty4,33028
|
91
90
|
skfolio/pre_selection/__init__.py,sha256=6J_D0QIMi24owwJJP6vxYnIgIyWZuMzCMnpMCEpAvCo,606
|
92
91
|
skfolio/pre_selection/_drop_correlated.py,sha256=4-PSd8R20Rcdyc8Zzcy9B2eRPEtaEkM3YXi74YKF-Pk,3839
|
93
92
|
skfolio/pre_selection/_drop_zero_variance.py,sha256=66Mi0Fta1kdmLw0CCqa7p9AqpoBpS9B3fGPLqhb8VIU,2312
|
@@ -97,25 +96,28 @@ skfolio/pre_selection/_select_non_dominated.py,sha256=Auv7G8E1QNO96heb35oBWmFLd6
|
|
97
96
|
skfolio/pre_selection/_select_non_expiring.py,sha256=g7ekl69MxQFkg06Km3NiuJXr0JfYPqvo5hMLQsIcUKY,5101
|
98
97
|
skfolio/preprocessing/__init__.py,sha256=94jMyP_E7FlwQVE8D_bXDi8KyfAA2xPHTDvYOi6zf_g,123
|
99
98
|
skfolio/preprocessing/_returns.py,sha256=6G5qJIVHGnIoeBNAqpJTB-569g9NeXVIyrz033bK5Gk,4576
|
100
|
-
skfolio/prior/__init__.py,sha256=
|
101
|
-
skfolio/prior/_base.py,sha256=
|
102
|
-
skfolio/prior/_black_litterman.py,sha256=
|
103
|
-
skfolio/prior/_empirical.py,sha256=
|
104
|
-
skfolio/prior/
|
105
|
-
skfolio/prior/
|
106
|
-
skfolio/
|
99
|
+
skfolio/prior/__init__.py,sha256=4bi4u7Y-D9vLKb0nxlAXYEZUuYkjPXQcC7qlYUu_DMA,720
|
100
|
+
skfolio/prior/_base.py,sha256=aSqyhBYc35RpWq4XpM3UsOu88Bvxbqn7QhctK9bP0I0,2217
|
101
|
+
skfolio/prior/_black_litterman.py,sha256=oRH6wUjsL5bkjiNbVtxaIPMNi34rqPp7WBmDbJiklKM,10675
|
102
|
+
skfolio/prior/_empirical.py,sha256=zRceQNsbeWdkZHIaFvO91AhZTqkPd8YE2f60cK39M-U,7486
|
103
|
+
skfolio/prior/_entropy_pooling.py,sha256=x4qd-bU52ZGePwduPNY5fBhWwrNHfI77oWp5x0KyLPc,59285
|
104
|
+
skfolio/prior/_factor_model.py,sha256=lbXFsuidDJvLBX7fwp6fXXvgdL3MzkL5lJCx7HEABcA,12241
|
105
|
+
skfolio/prior/_opinion_pooling.py,sha256=dBZ8TjlAOKWA9lZFD4DS_PH5HG8iZYgtNrJLPnqwX0o,19055
|
106
|
+
skfolio/prior/_synthetic_data.py,sha256=HsxcIQa9lwcYh81Y-o00xsSr9-djgrTQtRTE4SCNpKo,8735
|
107
107
|
skfolio/uncertainty_set/__init__.py,sha256=SHbOq0ip3vuwEK9G4pzz0GncDbGsHw7ywF9tPnkUrZ8,648
|
108
108
|
skfolio/uncertainty_set/_base.py,sha256=R6qH8Zg5Ti3Qny-guL4Js8rY9JhpF8jMwV_w9HCbgWI,4307
|
109
|
-
skfolio/uncertainty_set/_bootstrap.py,sha256=
|
110
|
-
skfolio/uncertainty_set/_empirical.py,sha256=
|
109
|
+
skfolio/uncertainty_set/_bootstrap.py,sha256=6cSTae56zFAAS4WCxEm3IO2FdVk3JHbCOJc7IOIx8b4,11297
|
110
|
+
skfolio/uncertainty_set/_empirical.py,sha256=ZmCk6OBMxbii3TyFg3tZRHWijU7fH1R0R-WS-jb3gj4,9444
|
111
111
|
skfolio/utils/__init__.py,sha256=bC6-MsCVF7xKTr48z7OzJJUeWvqAB7BiHeNTiKsme70,20
|
112
112
|
skfolio/utils/bootstrap.py,sha256=6BN_9CgfbeImBSNEE0dF52FRGuQT41HcQXeHPLwFqJc,3565
|
113
|
-
skfolio/utils/
|
113
|
+
skfolio/utils/composition.py,sha256=e0dWCEIYnho3HU2KGGS9UHQdycdVuqMcTe7hi0LihjQ,3416
|
114
|
+
skfolio/utils/equations.py,sha256=N4C937GesVEwC3PWecP2oSpg6myayA58fWsNGjhEbpw,15812
|
115
|
+
skfolio/utils/figure.py,sha256=2U0PuHRuza_1N6o_fWD8amNDc0IhgfzM5owFl3zBzwg,5744
|
114
116
|
skfolio/utils/sorting.py,sha256=F7gfIBfnulfDUiqvzrlR-pba4PPLJT6NH7-5s4sdRhw,3521
|
115
117
|
skfolio/utils/stats.py,sha256=glVHo7rjwy06dl5kkULLOADMrEkVJcfXXAz-1qmYQL4,17005
|
116
|
-
skfolio/utils/tools.py,sha256=
|
117
|
-
skfolio-0.
|
118
|
-
skfolio-0.
|
119
|
-
skfolio-0.
|
120
|
-
skfolio-0.
|
121
|
-
skfolio-0.
|
118
|
+
skfolio/utils/tools.py,sha256=XQ-bkbhIqBTuv_ZLK-vDnDx8NGFCFvmWoJF8Ui1tj38,23020
|
119
|
+
skfolio-0.10.1.dist-info/licenses/LICENSE,sha256=F6Gi-ZJX5BlVzYK8R9NcvAkAsKa7KO29xB1OScbrH6Q,1526
|
120
|
+
skfolio-0.10.1.dist-info/METADATA,sha256=LwEyMOUgw9X1asJvXVAVvkNSYeKXvqo35bZsZBFQ28o,25136
|
121
|
+
skfolio-0.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
122
|
+
skfolio-0.10.1.dist-info/top_level.txt,sha256=NXEaoS9Ms7t32gxkb867nV0OKlU0KmssL7IJBVo0fJs,8
|
123
|
+
skfolio-0.10.1.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
"""Synthetic Data module."""
|
File without changes
|
File without changes
|
File without changes
|