skfolio 0.10.0__py3-none-any.whl → 0.10.2__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/measures/_measures.py +4 -2
- skfolio/prior/_empirical.py +1 -0
- skfolio/prior/_entropy_pooling.py +9 -9
- {skfolio-0.10.0.dist-info → skfolio-0.10.2.dist-info}/METADATA +53 -21
- {skfolio-0.10.0.dist-info → skfolio-0.10.2.dist-info}/RECORD +8 -8
- {skfolio-0.10.0.dist-info → skfolio-0.10.2.dist-info}/WHEEL +0 -0
- {skfolio-0.10.0.dist-info → skfolio-0.10.2.dist-info}/licenses/LICENSE +0 -0
- {skfolio-0.10.0.dist-info → skfolio-0.10.2.dist-info}/top_level.txt +0 -0
skfolio/measures/_measures.py
CHANGED
@@ -136,7 +136,9 @@ def variance(
|
|
136
136
|
if sample_weight is None:
|
137
137
|
return np.var(returns, ddof=0 if biased else 1, axis=0)
|
138
138
|
|
139
|
-
biased_var =
|
139
|
+
biased_var = (
|
140
|
+
sample_weight @ (returns - mean(returns, sample_weight=sample_weight)) ** 2
|
141
|
+
)
|
140
142
|
if biased:
|
141
143
|
return biased_var
|
142
144
|
n_eff = 1 / np.sum(sample_weight**2)
|
@@ -177,7 +179,7 @@ def semi_variance(
|
|
177
179
|
If `returns` is a 2D-array, the result is a ndarray of shape (n_assets,).
|
178
180
|
"""
|
179
181
|
if min_acceptable_return is None:
|
180
|
-
min_acceptable_return = mean(returns)
|
182
|
+
min_acceptable_return = mean(returns, sample_weight=sample_weight)
|
181
183
|
|
182
184
|
biased_semi_var = mean(
|
183
185
|
np.maximum(0, min_acceptable_return - returns) ** 2, sample_weight=sample_weight
|
skfolio/prior/_empirical.py
CHANGED
@@ -191,6 +191,7 @@ class EmpiricalPrior(BasePrior):
|
|
191
191
|
# horizon
|
192
192
|
mu = np.exp(mu + 0.5 * np.diag(covariance))
|
193
193
|
covariance = np.outer(mu, mu) * (np.exp(covariance) - 1)
|
194
|
+
mu -= 1
|
194
195
|
|
195
196
|
# we validate and convert to numpy after all models have been fitted to keep
|
196
197
|
# features names information.
|
@@ -589,10 +589,10 @@ class EntropyPooling(BasePrior):
|
|
589
589
|
|
590
590
|
Parameters
|
591
591
|
----------
|
592
|
-
a : ndarray of shape (n_observations,
|
592
|
+
a : ndarray of shape (n_observations, n_constraints)
|
593
593
|
Left matrix in `x @ a == b` or `x @ a <= b`.
|
594
594
|
|
595
|
-
a : ndarray of shape (n_observations,
|
595
|
+
a : ndarray of shape (n_observations, n_constraints)
|
596
596
|
Right vector in `x @ a == b` or `x @ a <= b`.
|
597
597
|
|
598
598
|
Returns
|
@@ -1029,16 +1029,16 @@ class EntropyPooling(BasePrior):
|
|
1029
1029
|
|
1030
1030
|
"""
|
1031
1031
|
n_observations, _ = self._returns.shape
|
1032
|
-
# Init
|
1032
|
+
# Init constraints with sum(p)==1, rescaled by its norm
|
1033
1033
|
# Has better convergence than the normalized form done inside the dual.
|
1034
1034
|
a = [np.ones(n_observations).reshape(-1, 1) / np.sqrt(n_observations)]
|
1035
1035
|
b = [np.array([1.0]) / np.sqrt(n_observations)]
|
1036
1036
|
bounds = [(None, None)]
|
1037
|
-
for name,
|
1038
|
-
if
|
1039
|
-
a.append(
|
1040
|
-
b.append(
|
1041
|
-
s =
|
1037
|
+
for name, constraints in self._constraints.items():
|
1038
|
+
if constraints is not None:
|
1039
|
+
a.append(constraints[0])
|
1040
|
+
b.append(constraints[1])
|
1041
|
+
s = constraints[1].size
|
1042
1042
|
match name:
|
1043
1043
|
case "equality" | "cvar_equality":
|
1044
1044
|
bounds += [(None, None)] * s
|
@@ -1145,7 +1145,7 @@ class EntropyPooling(BasePrior):
|
|
1145
1145
|
|
1146
1146
|
if self._constraints["fixed_equality"] is not None:
|
1147
1147
|
a, b = self._constraints["fixed_equality"]
|
1148
|
-
#
|
1148
|
+
# Relax the problem with slack variables with a norm1 penalty to avoid
|
1149
1149
|
# solver infeasibility that may arise from overly tight constraints from
|
1150
1150
|
# fixing the moments.
|
1151
1151
|
slack = cp.Variable(b.size)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: skfolio
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.2
|
4
4
|
Summary: Portfolio optimization built on top of scikit-learn
|
5
5
|
Author-email: Hugo Delatte <delatte.hugo@gmail.com>
|
6
|
-
Maintainer-email: Hugo Delatte <delatte.hugo@gmail.com>, Matteo Manzi <matteomanzi09@gmail.com>
|
6
|
+
Maintainer-email: Hugo Delatte <delatte.hugo@gmail.com>, Matteo Manzi <matteomanzi09@gmail.com>, Carlo Nicolini <c.nicolini@ipazia.com>
|
7
7
|
License: BSD 3-Clause License
|
8
8
|
|
9
9
|
Copyright (c) 2007-2023 The skfolio developers.
|
@@ -176,7 +176,6 @@ Installation
|
|
176
176
|
pip install -U skfolio
|
177
177
|
|
178
178
|
|
179
|
-
|
180
179
|
Dependencies
|
181
180
|
~~~~~~~~~~~~
|
182
181
|
|
@@ -192,6 +191,23 @@ Dependencies
|
|
192
191
|
- joblib (>= |JoblibMinVersion|)
|
193
192
|
- plotly (>= |PlotlyMinVersion|)
|
194
193
|
|
194
|
+
Docker
|
195
|
+
~~~~~~
|
196
|
+
|
197
|
+
You can also spin up a reproducible JupyterLab environment using Docker:
|
198
|
+
|
199
|
+
Build the image::
|
200
|
+
|
201
|
+
docker build -t skfolio-jupyterlab .
|
202
|
+
|
203
|
+
Run the container::
|
204
|
+
|
205
|
+
docker run -p 8888:8888 -v <path-to-your-folder-containing-data>:/app/data -it skfolio-jupyterlab
|
206
|
+
|
207
|
+
Browse:
|
208
|
+
|
209
|
+
Open localhost:8888/lab and start using `skfolio`
|
210
|
+
|
195
211
|
Key Concepts
|
196
212
|
~~~~~~~~~~~~
|
197
213
|
Since the development of modern portfolio theory by Markowitz (1952), mean-variance
|
@@ -385,6 +401,7 @@ Imports
|
|
385
401
|
)
|
386
402
|
from skfolio.optimization import (
|
387
403
|
MeanRisk,
|
404
|
+
HierarchicalRiskParity,
|
388
405
|
NestedClustersOptimization,
|
389
406
|
ObjectiveFunction,
|
390
407
|
RiskBudgeting,
|
@@ -584,11 +601,13 @@ Factor Model
|
|
584
601
|
|
585
602
|
factor_prices = load_factors_dataset()
|
586
603
|
|
587
|
-
X,
|
588
|
-
X_train, X_test,
|
604
|
+
X, factors = prices_to_returns(prices, factor_prices)
|
605
|
+
X_train, X_test, factors_train, factors_test = train_test_split(
|
606
|
+
X, factors, test_size=0.33, shuffle=False
|
607
|
+
)
|
589
608
|
|
590
609
|
model = MeanRisk(prior_estimator=FactorModel())
|
591
|
-
model.fit(X_train,
|
610
|
+
model.fit(X_train, factors_train)
|
592
611
|
|
593
612
|
print(model.weights_)
|
594
613
|
|
@@ -597,7 +616,6 @@ Factor Model
|
|
597
616
|
print(portfolio.calmar_ratio)
|
598
617
|
print(portfolio.summary())
|
599
618
|
|
600
|
-
|
601
619
|
Factor Model & Covariance Detoning
|
602
620
|
----------------------------------
|
603
621
|
.. code-block:: python
|
@@ -658,7 +676,7 @@ Combinatorial Purged Cross-Validation
|
|
658
676
|
|
659
677
|
cv = CombinatorialPurgedCV(n_folds=10, n_test_folds=2)
|
660
678
|
|
661
|
-
print(cv.
|
679
|
+
print(cv.summary(X_train))
|
662
680
|
|
663
681
|
population = cross_val_predict(model, X_train, cv=cv)
|
664
682
|
|
@@ -674,7 +692,7 @@ Minimum CVaR Optimization on Synthetic Returns
|
|
674
692
|
.. code-block:: python
|
675
693
|
|
676
694
|
vine = VineCopula(log_transform=True, n_jobs=-1)
|
677
|
-
prior =
|
695
|
+
prior = SyntheticData(distribution_estimator=vine, n_samples=2000)
|
678
696
|
model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=prior)
|
679
697
|
model.fit(X)
|
680
698
|
print(model.weights_)
|
@@ -684,7 +702,7 @@ Stress Test
|
|
684
702
|
-----------
|
685
703
|
.. code-block:: python
|
686
704
|
|
687
|
-
vine = VineCopula(log_transform=True, central_assets=["BAC"]
|
705
|
+
vine = VineCopula(log_transform=True, central_assets=["BAC"], n_jobs=-1)
|
688
706
|
vine.fit(X)
|
689
707
|
X_stressed = vine.sample(n_samples=10_000, conditioning = {"BAC": -0.2})
|
690
708
|
ptf_stressed = model.predict(X_stressed)
|
@@ -702,7 +720,7 @@ Minimum CVaR Optimization on Synthetic Factors
|
|
702
720
|
)
|
703
721
|
factor_model = FactorModel(factor_prior_estimator=factor_prior)
|
704
722
|
model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=factor_model)
|
705
|
-
model.fit(X,
|
723
|
+
model.fit(X, factors)
|
706
724
|
print(model.weights_)
|
707
725
|
|
708
726
|
|
@@ -713,7 +731,7 @@ Factor Stress Test
|
|
713
731
|
factor_model.set_params(factor_prior_estimator__sample_args=dict(
|
714
732
|
conditioning={"QUAL": -0.5}
|
715
733
|
))
|
716
|
-
factor_model.fit(X,
|
734
|
+
factor_model.fit(X, factors)
|
717
735
|
stressed_dist = factor_model.return_distribution_
|
718
736
|
stressed_ptf = model.predict(stressed_dist)
|
719
737
|
|
@@ -798,7 +816,7 @@ Recognition
|
|
798
816
|
~~~~~~~~~~~
|
799
817
|
|
800
818
|
We would like to thank all contributors to our direct dependencies, such as
|
801
|
-
scikit-learn and cvxpy, as well as the contributors of the following resources that
|
819
|
+
`scikit-learn <https://github.com/scikit-learn/scikit-learn>`_ and `cvxpy <https://github.com/cvxpy/cvxpy>`_, as well as the contributors of the following resources that
|
802
820
|
served as sources of inspiration:
|
803
821
|
|
804
822
|
* PyPortfolioOpt
|
@@ -807,6 +825,7 @@ served as sources of inspiration:
|
|
807
825
|
* microprediction
|
808
826
|
* statsmodels
|
809
827
|
* rsome
|
828
|
+
* danielppalomar.com
|
810
829
|
* gautier.marti.ai
|
811
830
|
|
812
831
|
|
@@ -815,12 +834,25 @@ Citation
|
|
815
834
|
|
816
835
|
If you use `skfolio` in a scientific publication, we would appreciate citations:
|
817
836
|
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
837
|
+
**The library**::
|
838
|
+
|
839
|
+
@software{skfolio,
|
840
|
+
title = {skfolio},
|
841
|
+
author = {Delatte, Hugo and Nicolini, Carlo and Manzi, Matteo},
|
842
|
+
year = {2024},
|
843
|
+
doi = {TBD after next release},
|
844
|
+
url = {https://github.com/skfolio/skfolio}
|
845
|
+
}
|
846
|
+
|
847
|
+
**The paper**::
|
848
|
+
|
849
|
+
@article{nicolini2025skfolio,
|
850
|
+
title = {skfolio: Portfolio Optimization in Python},
|
851
|
+
author = {Nicolini, Carlo and Manzi, Matteo and Delatte, Hugo},
|
852
|
+
journal = {arXiv preprint arXiv:2507.04176},
|
853
|
+
year = {2025},
|
854
|
+
eprint = {2507.04176},
|
855
|
+
archivePrefix = {arXiv},
|
856
|
+
url = {https://arxiv.org/abs/2507.04176}
|
857
|
+
}
|
826
858
|
|
@@ -37,7 +37,7 @@ skfolio/distribution/univariate/_selection.py,sha256=6KL4gngiLKwaBUpCDX19ABOkMBz
|
|
37
37
|
skfolio/distribution/univariate/_student_t.py,sha256=GcI4fKp6q5XegfvT_i3AqfWlUMxCq7A5sX6Xsf4pye8,4553
|
38
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
|
@@ -99,8 +99,8 @@ skfolio/preprocessing/_returns.py,sha256=6G5qJIVHGnIoeBNAqpJTB-569g9NeXVIyrz033b
|
|
99
99
|
skfolio/prior/__init__.py,sha256=4bi4u7Y-D9vLKb0nxlAXYEZUuYkjPXQcC7qlYUu_DMA,720
|
100
100
|
skfolio/prior/_base.py,sha256=aSqyhBYc35RpWq4XpM3UsOu88Bvxbqn7QhctK9bP0I0,2217
|
101
101
|
skfolio/prior/_black_litterman.py,sha256=oRH6wUjsL5bkjiNbVtxaIPMNi34rqPp7WBmDbJiklKM,10675
|
102
|
-
skfolio/prior/_empirical.py,sha256=
|
103
|
-
skfolio/prior/_entropy_pooling.py,sha256=
|
102
|
+
skfolio/prior/_empirical.py,sha256=f_5PTd3orjIXIaqWEs9fPffnStNLXe3Y_-PZ1dc0p3U,7506
|
103
|
+
skfolio/prior/_entropy_pooling.py,sha256=x4qd-bU52ZGePwduPNY5fBhWwrNHfI77oWp5x0KyLPc,59285
|
104
104
|
skfolio/prior/_factor_model.py,sha256=lbXFsuidDJvLBX7fwp6fXXvgdL3MzkL5lJCx7HEABcA,12241
|
105
105
|
skfolio/prior/_opinion_pooling.py,sha256=dBZ8TjlAOKWA9lZFD4DS_PH5HG8iZYgtNrJLPnqwX0o,19055
|
106
106
|
skfolio/prior/_synthetic_data.py,sha256=HsxcIQa9lwcYh81Y-o00xsSr9-djgrTQtRTE4SCNpKo,8735
|
@@ -116,8 +116,8 @@ skfolio/utils/figure.py,sha256=2U0PuHRuza_1N6o_fWD8amNDc0IhgfzM5owFl3zBzwg,5744
|
|
116
116
|
skfolio/utils/sorting.py,sha256=F7gfIBfnulfDUiqvzrlR-pba4PPLJT6NH7-5s4sdRhw,3521
|
117
117
|
skfolio/utils/stats.py,sha256=glVHo7rjwy06dl5kkULLOADMrEkVJcfXXAz-1qmYQL4,17005
|
118
118
|
skfolio/utils/tools.py,sha256=XQ-bkbhIqBTuv_ZLK-vDnDx8NGFCFvmWoJF8Ui1tj38,23020
|
119
|
-
skfolio-0.10.
|
120
|
-
skfolio-0.10.
|
121
|
-
skfolio-0.10.
|
122
|
-
skfolio-0.10.
|
123
|
-
skfolio-0.10.
|
119
|
+
skfolio-0.10.2.dist-info/licenses/LICENSE,sha256=F6Gi-ZJX5BlVzYK8R9NcvAkAsKa7KO29xB1OScbrH6Q,1526
|
120
|
+
skfolio-0.10.2.dist-info/METADATA,sha256=GO42LTbqL_TT9Ml1TWGrTTIM_KPKg0lTx_yyoYxqxgU,26181
|
121
|
+
skfolio-0.10.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
122
|
+
skfolio-0.10.2.dist-info/top_level.txt,sha256=NXEaoS9Ms7t32gxkb867nV0OKlU0KmssL7IJBVo0fJs,8
|
123
|
+
skfolio-0.10.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|