skfolio 0.7.0__tar.gz → 0.8.0__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.
Files changed (128) hide show
  1. {skfolio-0.7.0/src/skfolio.egg-info → skfolio-0.8.0}/PKG-INFO +99 -24
  2. {skfolio-0.7.0 → skfolio-0.8.0}/README.rst +87 -14
  3. {skfolio-0.7.0 → skfolio-0.8.0}/pyproject.toml +18 -10
  4. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/__init__.py +2 -2
  5. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/cluster/__init__.py +1 -1
  6. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/cluster/_hierarchical.py +1 -1
  7. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/datasets/__init__.py +1 -1
  8. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/datasets/_base.py +2 -2
  9. skfolio-0.8.0/src/skfolio/datasets/data/__init__.py +1 -0
  10. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/distance/__init__.py +1 -1
  11. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/distance/_base.py +2 -2
  12. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/distance/_distance.py +4 -4
  13. skfolio-0.8.0/src/skfolio/distribution/__init__.py +56 -0
  14. skfolio-0.8.0/src/skfolio/distribution/_base.py +203 -0
  15. skfolio-0.8.0/src/skfolio/distribution/copula/__init__.py +35 -0
  16. skfolio-0.8.0/src/skfolio/distribution/copula/_base.py +456 -0
  17. skfolio-0.8.0/src/skfolio/distribution/copula/_clayton.py +539 -0
  18. skfolio-0.8.0/src/skfolio/distribution/copula/_gaussian.py +407 -0
  19. skfolio-0.8.0/src/skfolio/distribution/copula/_gumbel.py +560 -0
  20. skfolio-0.8.0/src/skfolio/distribution/copula/_independent.py +196 -0
  21. skfolio-0.8.0/src/skfolio/distribution/copula/_joe.py +609 -0
  22. skfolio-0.8.0/src/skfolio/distribution/copula/_selection.py +111 -0
  23. skfolio-0.8.0/src/skfolio/distribution/copula/_student_t.py +486 -0
  24. skfolio-0.8.0/src/skfolio/distribution/copula/_utils.py +509 -0
  25. skfolio-0.8.0/src/skfolio/distribution/multivariate/__init__.py +11 -0
  26. skfolio-0.8.0/src/skfolio/distribution/multivariate/_base.py +241 -0
  27. skfolio-0.8.0/src/skfolio/distribution/multivariate/_utils.py +632 -0
  28. skfolio-0.8.0/src/skfolio/distribution/multivariate/_vine_copula.py +1254 -0
  29. skfolio-0.8.0/src/skfolio/distribution/univariate/__init__.py +19 -0
  30. skfolio-0.8.0/src/skfolio/distribution/univariate/_base.py +308 -0
  31. skfolio-0.8.0/src/skfolio/distribution/univariate/_gaussian.py +136 -0
  32. skfolio-0.8.0/src/skfolio/distribution/univariate/_johnson_su.py +152 -0
  33. skfolio-0.8.0/src/skfolio/distribution/univariate/_normal_inverse_gaussian.py +153 -0
  34. skfolio-0.8.0/src/skfolio/distribution/univariate/_selection.py +85 -0
  35. skfolio-0.8.0/src/skfolio/distribution/univariate/_student_t.py +144 -0
  36. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/exceptions.py +6 -6
  37. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/measures/__init__.py +1 -1
  38. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/measures/_enums.py +7 -7
  39. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/measures/_measures.py +4 -7
  40. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/metrics/__init__.py +2 -0
  41. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/metrics/_scorer.py +4 -4
  42. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/model_selection/__init__.py +2 -2
  43. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/model_selection/_combinatorial.py +15 -12
  44. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/model_selection/_validation.py +2 -2
  45. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/model_selection/_walk_forward.py +3 -3
  46. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_base.py +1 -1
  47. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_denoise_covariance.py +1 -1
  48. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_detone_covariance.py +1 -1
  49. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_empirical_covariance.py +1 -1
  50. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_ew_covariance.py +1 -1
  51. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_gerber_covariance.py +1 -1
  52. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_graphical_lasso_cv.py +1 -1
  53. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_implied_covariance.py +2 -7
  54. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_ledoit_wolf.py +1 -1
  55. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_oas.py +1 -1
  56. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/_shrunk_covariance.py +1 -1
  57. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/expected_returns/_base.py +1 -1
  58. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/expected_returns/_empirical_mu.py +1 -1
  59. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/expected_returns/_equilibrium_mu.py +1 -1
  60. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/expected_returns/_ew_mu.py +1 -1
  61. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/expected_returns/_shrunk_mu.py +2 -2
  62. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/__init__.py +2 -0
  63. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/_base.py +2 -2
  64. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/cluster/__init__.py +2 -0
  65. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/cluster/_nco.py +7 -7
  66. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/cluster/hierarchical/__init__.py +2 -0
  67. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/cluster/hierarchical/_base.py +1 -2
  68. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/cluster/hierarchical/_herc.py +2 -2
  69. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/cluster/hierarchical/_hrp.py +2 -2
  70. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/convex/__init__.py +2 -0
  71. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/convex/_base.py +8 -8
  72. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/convex/_distributionally_robust.py +4 -4
  73. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/convex/_maximum_diversification.py +5 -5
  74. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/convex/_mean_risk.py +5 -6
  75. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/convex/_risk_budgeting.py +3 -3
  76. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/ensemble/__init__.py +2 -0
  77. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/ensemble/_base.py +2 -2
  78. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/ensemble/_stacking.py +1 -1
  79. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/naive/__init__.py +2 -0
  80. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/optimization/naive/_naive.py +1 -1
  81. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/population/__init__.py +2 -0
  82. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/population/_population.py +34 -7
  83. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/portfolio/_base.py +42 -8
  84. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/portfolio/_multi_period_portfolio.py +3 -2
  85. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/portfolio/_portfolio.py +4 -4
  86. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/pre_selection/__init__.py +2 -0
  87. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/pre_selection/_drop_correlated.py +2 -2
  88. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/pre_selection/_select_complete.py +25 -26
  89. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/pre_selection/_select_k_extremes.py +2 -2
  90. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/pre_selection/_select_non_dominated.py +2 -2
  91. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/pre_selection/_select_non_expiring.py +2 -2
  92. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/preprocessing/__init__.py +2 -0
  93. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/preprocessing/_returns.py +2 -2
  94. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/prior/__init__.py +4 -0
  95. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/prior/_base.py +2 -2
  96. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/prior/_black_litterman.py +5 -3
  97. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/prior/_empirical.py +3 -1
  98. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/prior/_factor_model.py +8 -4
  99. skfolio-0.8.0/src/skfolio/prior/_synthetic_data.py +239 -0
  100. skfolio-0.8.0/src/skfolio/synthetic_returns/__init__.py +1 -0
  101. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/typing.py +1 -1
  102. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/uncertainty_set/__init__.py +2 -0
  103. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/uncertainty_set/_base.py +2 -2
  104. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/uncertainty_set/_bootstrap.py +1 -1
  105. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/uncertainty_set/_empirical.py +1 -1
  106. skfolio-0.8.0/src/skfolio/utils/__init__.py +1 -0
  107. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/utils/bootstrap.py +2 -2
  108. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/utils/equations.py +13 -10
  109. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/utils/sorting.py +2 -2
  110. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/utils/stats.py +7 -7
  111. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/utils/tools.py +76 -12
  112. {skfolio-0.7.0 → skfolio-0.8.0/src/skfolio.egg-info}/PKG-INFO +99 -24
  113. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio.egg-info/SOURCES.txt +25 -0
  114. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio.egg-info/requires.txt +9 -8
  115. skfolio-0.7.0/src/skfolio/datasets/data/__init__.py +0 -0
  116. skfolio-0.7.0/src/skfolio/utils/__init__.py +0 -0
  117. {skfolio-0.7.0 → skfolio-0.8.0}/LICENSE +0 -0
  118. {skfolio-0.7.0 → skfolio-0.8.0}/MANIFEST.in +0 -0
  119. {skfolio-0.7.0 → skfolio-0.8.0}/setup.cfg +0 -0
  120. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/datasets/data/factors_dataset.csv.gz +0 -0
  121. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/datasets/data/sp500_dataset.csv.gz +0 -0
  122. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/datasets/data/sp500_index.csv.gz +0 -0
  123. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/__init__.py +0 -0
  124. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/covariance/__init__.py +0 -0
  125. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/moments/expected_returns/__init__.py +0 -0
  126. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio/portfolio/__init__.py +0 -0
  127. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio.egg-info/dependency_links.txt +0 -0
  128. {skfolio-0.7.0 → skfolio-0.8.0}/src/skfolio.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: skfolio
3
- Version: 0.7.0
3
+ Version: 0.8.0
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>
@@ -57,20 +57,21 @@ Requires-Python: >=3.10
57
57
  Description-Content-Type: text/x-rst
58
58
  License-File: LICENSE
59
59
  Requires-Dist: numpy>=1.23.4
60
- Requires-Dist: scipy>=1.8.0
60
+ Requires-Dist: scipy>=1.15.2
61
61
  Requires-Dist: pandas>=1.4.1
62
- Requires-Dist: cvxpy>=1.4.1
62
+ Requires-Dist: cvxpy-base>=1.5.0
63
+ Requires-Dist: clarabel>=0.9.0
63
64
  Requires-Dist: scikit-learn>=1.6.0
64
65
  Requires-Dist: joblib>=1.3.2
65
66
  Requires-Dist: plotly>=5.22.0
66
67
  Provides-Extra: dev
67
- Requires-Dist: cvxpy[SCIP]<2.0.0,>=1.6.0; extra == "dev"
68
- Requires-Dist: pytest<9.0.0,>=8.3.4; extra == "dev"
69
- Requires-Dist: pytest-cov<7.0.0,>=6.0.0; extra == "dev"
70
- Requires-Dist: ruff<1.0.0,>=0.8.4; extra == "dev"
71
- Requires-Dist: pre-commit<4.2.0,>=4.0.0; extra == "dev"
68
+ Requires-Dist: PySCIPOpt; extra == "dev"
69
+ Requires-Dist: pytest; extra == "dev"
70
+ Requires-Dist: pytest-cov; extra == "dev"
71
+ Requires-Dist: ruff; extra == "dev"
72
+ Requires-Dist: pre-commit; extra == "dev"
72
73
  Provides-Extra: docs
73
- Requires-Dist: cvxpy[SCIP]; extra == "docs"
74
+ Requires-Dist: PySCIPOpt; extra == "docs"
74
75
  Requires-Dist: Sphinx; extra == "docs"
75
76
  Requires-Dist: sphinx-gallery; extra == "docs"
76
77
  Requires-Dist: sphinx-design; extra == "docs"
@@ -87,6 +88,7 @@ Requires-Dist: sphinx-sitemap; extra == "docs"
87
88
  Requires-Dist: jupyterlite-sphinx; extra == "docs"
88
89
  Requires-Dist: jupyterlite-pyodide-kernel; extra == "docs"
89
90
  Requires-Dist: nbformat; extra == "docs"
91
+ Dynamic: license-file
90
92
 
91
93
  .. -*- mode: rst -*-
92
94
 
@@ -129,8 +131,9 @@ Requires-Dist: nbformat; extra == "docs"
129
131
  .. |NumpyMinVersion| replace:: 1.23.4
130
132
  .. |ScipyMinVersion| replace:: 1.8.0
131
133
  .. |PandasMinVersion| replace:: 1.4.1
132
- .. |CvxpyMinVersion| replace:: 1.4.1
133
- .. |SklearnMinVersion| replace:: 1.5.0
134
+ .. |CvxpyBaseMinVersion| replace:: 1.5.0
135
+ .. |ClarabelMinVersion| replace:: 0.9.0
136
+ .. |SklearnMinVersion| replace:: 1.6.0
134
137
  .. |JoblibMinVersion| replace:: 1.3.2
135
138
  .. |PlotlyMinVersion| replace:: 5.22.0
136
139
 
@@ -148,7 +151,7 @@ Requires-Dist: nbformat; extra == "docs"
148
151
  It offers a unified interface and tools compatible with scikit-learn to build, fine-tune,
149
152
  and cross-validate portfolio models.
150
153
 
151
- It is distributed under the open source 3-Clause BSD license.
154
+ It is distributed under the open-source 3-Clause BSD license.
152
155
 
153
156
  .. image:: https://raw.githubusercontent.com/skfolio/skfolio/master/docs/_static/expo.jpg
154
157
  :target: https://skfolio.org/auto_examples/
@@ -180,7 +183,8 @@ Dependencies
180
183
  - numpy (>= |NumpyMinVersion|)
181
184
  - scipy (>= |ScipyMinVersion|)
182
185
  - pandas (>= |PandasMinVersion|)
183
- - cvxpy (>= |CvxpyMinVersion|)
186
+ - cvxpy-base (>= |CvxpyBaseMinVersion|)
187
+ - clarabel (>= |ClarabelMinVersion|)
184
188
  - scikit-learn (>= |SklearnMinVersion|)
185
189
  - joblib (>= |JoblibMinVersion|)
186
190
  - plotly (>= |PlotlyMinVersion|)
@@ -194,7 +198,7 @@ Unfortunately, it faces a number of shortcomings, including high sensitivity to
194
198
  input parameters (expected returns and covariance), weight concentration, high turnover,
195
199
  and poor out-of-sample performance.
196
200
 
197
- It is well known that naive allocation (1/N, inverse-vol, etc.) tends to outperform
201
+ It is well-known that naive allocation (1/N, inverse-vol, etc.) tends to outperform
198
202
  MVO out-of-sample (DeMiguel, 2007).
199
203
 
200
204
  Numerous approaches have been developed to alleviate these shortcomings (shrinkage,
@@ -203,10 +207,10 @@ approaches, coherent risk measures, left-tail risk optimization, distributionall
203
207
  optimization, factor model, risk-parity, hierarchical clustering, ensemble methods,
204
208
  pre-selection, etc.).
205
209
 
206
- With this large number of methods, added to the fact that they can be composed together,
207
- there is a need for a unified framework with a machine learning approach to perform
208
- model selection, validation, and parameter tuning while reducing the risk of data
209
- leakage and overfitting.
210
+ Given the large number of methods, and the fact that they can be combined, there is a
211
+ need for a unified framework with a machine-learning approach to perform model
212
+ selection, validation, and parameter tuning while mitigating the risk of data leakage
213
+ and overfitting.
210
214
 
211
215
  This framework is built on scikit-learn's API.
212
216
 
@@ -256,10 +260,27 @@ Available models
256
260
  * Distance Correlation
257
261
  * Variation of Information
258
262
 
263
+ * Distribution Estimator:
264
+ * Univariate:
265
+ * Gaussian
266
+ * Student's t
267
+ * Johnson Su
268
+ * Normal Inverse Gaussian
269
+ * Bivariate Copula
270
+ * Gaussian Copula
271
+ * Student's t Copula
272
+ * Clayton Copula
273
+ * Gumbel Copula
274
+ * Joe Copula
275
+ * Independent Copula
276
+ * Multivariate
277
+ * Vine Copula (Regular, Centered, Clustered, Conditional Sampling)
278
+
259
279
  * Prior Estimator:
260
280
  * Empirical
261
281
  * Black & Litterman
262
282
  * Factor Model
283
+ * Synthetic Data (Stress Test, Factor Stress Test)
263
284
 
264
285
  * Uncertainty Set Estimator:
265
286
  * On Expected Returns:
@@ -267,12 +288,14 @@ Available models
267
288
  * Circular Bootstrap
268
289
  * On Covariance:
269
290
  * Empirical
270
- * Circular bootstrap
291
+ * Circular Bootstrap
271
292
 
272
293
  * Pre-Selection Transformer:
273
294
  * Non-Dominated Selection
274
295
  * Select K Extremes (Best or Worst)
275
296
  * Drop Highly Correlated Assets
297
+ * Select Non-Expiring Assets
298
+ * Select Complete Assets (handle late inception, delisting, etc.)
276
299
 
277
300
  * Cross-Validation and Model Selection:
278
301
  * Compatible with all `sklearn` methods (KFold, etc.)
@@ -317,6 +340,8 @@ Available models
317
340
  * Budget Constraints
318
341
  * Tracking Error Constraints
319
342
  * Turnover Constraints
343
+ * Cardinality and Group Cardinality Constraints
344
+ * Threshold (Long and Short) Constraints
320
345
 
321
346
  Quickstart
322
347
  ~~~~~~~~~~
@@ -339,6 +364,7 @@ Imports
339
364
 
340
365
  from skfolio import RatioMeasure, RiskMeasure
341
366
  from skfolio.datasets import load_factors_dataset, load_sp500_dataset
367
+ from skfolio.distribution import VineCopula
342
368
  from skfolio.model_selection import (
343
369
  CombinatorialPurgedCV,
344
370
  WalkForward,
@@ -359,7 +385,7 @@ Imports
359
385
  )
360
386
  from skfolio.pre_selection import SelectKExtremes
361
387
  from skfolio.preprocessing import prices_to_returns
362
- from skfolio.prior import BlackLitterman, EmpiricalPrior, FactorModel
388
+ from skfolio.prior import BlackLitterman, EmpiricalPrior, FactorModel, SyntheticData
363
389
  from skfolio.uncertainty_set import BootstrapMuUncertaintySet
364
390
 
365
391
  Load Dataset
@@ -630,12 +656,61 @@ Combinatorial Purged Cross-Validation
630
656
  print(population.summary())
631
657
 
632
658
 
659
+ Minimum CVaR Optimization on Synthetic Returns
660
+ ----------------------------------------------
661
+ .. code-block:: python
662
+
663
+ vine = VineCopula(log_transform=True, n_jobs=-1)
664
+ prior = =SyntheticData(distribution_estimator=vine, n_samples=2000)
665
+ model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=prior)
666
+ model.fit(X)
667
+ print(model.weights_)
668
+
669
+
670
+ Stress Test
671
+ -----------
672
+ .. code-block:: python
673
+
674
+ vine = VineCopula(log_transform=True, central_assets=["BAC"] n_jobs=-1)
675
+ vine.fit(X)
676
+ X_stressed = vine.sample(n_samples=10_000, conditioning = {"BAC": -0.2})
677
+ ptf_stressed = model.predict(X_stressed)
678
+
679
+
680
+ Minimum CVaR Optimization on Synthetic Factors
681
+ ----------------------------------------------
682
+ .. code-block:: python
683
+
684
+ vine = VineCopula(central_assets=["QUAL"], log_transform=True, n_jobs=-1)
685
+ factor_prior = SyntheticData(
686
+ distribution_estimator=vine,
687
+ n_samples=10_000,
688
+ sample_args=dict(conditioning={"QUAL": -0.2}),
689
+ )
690
+ factor_model = FactorModel(factor_prior_estimator=factor_prior)
691
+ model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=factor_model)
692
+ model.fit(X, y)
693
+ print(model.weights_)
694
+
695
+
696
+ Factor Stress Test
697
+ ------------------
698
+ .. code-block:: python
699
+
700
+ factor_model.set_params(factor_prior_estimator__sample_args=dict(
701
+ conditioning={"QUAL": -0.5}
702
+ ))
703
+ factor_model.fit(X,y)
704
+ stressed_X = factor_model.prior_model_.returns
705
+ stressed_ptf = model.predict(stressed_X)
706
+
707
+
633
708
  Recognition
634
709
  ~~~~~~~~~~~
635
710
 
636
- We would like to thank all contributors behind our direct dependencies, such as
637
- scikit-learn and cvxpy, but also the contributors of the following resources that were a
638
- source of inspiration:
711
+ We would like to thank all contributors to our direct dependencies, such as
712
+ scikit-learn and cvxpy, as well as the contributors of the following resources that
713
+ served as sources of inspiration:
639
714
 
640
715
  * PyPortfolioOpt
641
716
  * Riskfolio-Lib
@@ -39,8 +39,9 @@
39
39
  .. |NumpyMinVersion| replace:: 1.23.4
40
40
  .. |ScipyMinVersion| replace:: 1.8.0
41
41
  .. |PandasMinVersion| replace:: 1.4.1
42
- .. |CvxpyMinVersion| replace:: 1.4.1
43
- .. |SklearnMinVersion| replace:: 1.5.0
42
+ .. |CvxpyBaseMinVersion| replace:: 1.5.0
43
+ .. |ClarabelMinVersion| replace:: 0.9.0
44
+ .. |SklearnMinVersion| replace:: 1.6.0
44
45
  .. |JoblibMinVersion| replace:: 1.3.2
45
46
  .. |PlotlyMinVersion| replace:: 5.22.0
46
47
 
@@ -58,7 +59,7 @@
58
59
  It offers a unified interface and tools compatible with scikit-learn to build, fine-tune,
59
60
  and cross-validate portfolio models.
60
61
 
61
- It is distributed under the open source 3-Clause BSD license.
62
+ It is distributed under the open-source 3-Clause BSD license.
62
63
 
63
64
  .. image:: https://raw.githubusercontent.com/skfolio/skfolio/master/docs/_static/expo.jpg
64
65
  :target: https://skfolio.org/auto_examples/
@@ -90,7 +91,8 @@ Dependencies
90
91
  - numpy (>= |NumpyMinVersion|)
91
92
  - scipy (>= |ScipyMinVersion|)
92
93
  - pandas (>= |PandasMinVersion|)
93
- - cvxpy (>= |CvxpyMinVersion|)
94
+ - cvxpy-base (>= |CvxpyBaseMinVersion|)
95
+ - clarabel (>= |ClarabelMinVersion|)
94
96
  - scikit-learn (>= |SklearnMinVersion|)
95
97
  - joblib (>= |JoblibMinVersion|)
96
98
  - plotly (>= |PlotlyMinVersion|)
@@ -104,7 +106,7 @@ Unfortunately, it faces a number of shortcomings, including high sensitivity to
104
106
  input parameters (expected returns and covariance), weight concentration, high turnover,
105
107
  and poor out-of-sample performance.
106
108
 
107
- It is well known that naive allocation (1/N, inverse-vol, etc.) tends to outperform
109
+ It is well-known that naive allocation (1/N, inverse-vol, etc.) tends to outperform
108
110
  MVO out-of-sample (DeMiguel, 2007).
109
111
 
110
112
  Numerous approaches have been developed to alleviate these shortcomings (shrinkage,
@@ -113,10 +115,10 @@ approaches, coherent risk measures, left-tail risk optimization, distributionall
113
115
  optimization, factor model, risk-parity, hierarchical clustering, ensemble methods,
114
116
  pre-selection, etc.).
115
117
 
116
- With this large number of methods, added to the fact that they can be composed together,
117
- there is a need for a unified framework with a machine learning approach to perform
118
- model selection, validation, and parameter tuning while reducing the risk of data
119
- leakage and overfitting.
118
+ Given the large number of methods, and the fact that they can be combined, there is a
119
+ need for a unified framework with a machine-learning approach to perform model
120
+ selection, validation, and parameter tuning while mitigating the risk of data leakage
121
+ and overfitting.
120
122
 
121
123
  This framework is built on scikit-learn's API.
122
124
 
@@ -166,10 +168,27 @@ Available models
166
168
  * Distance Correlation
167
169
  * Variation of Information
168
170
 
171
+ * Distribution Estimator:
172
+ * Univariate:
173
+ * Gaussian
174
+ * Student's t
175
+ * Johnson Su
176
+ * Normal Inverse Gaussian
177
+ * Bivariate Copula
178
+ * Gaussian Copula
179
+ * Student's t Copula
180
+ * Clayton Copula
181
+ * Gumbel Copula
182
+ * Joe Copula
183
+ * Independent Copula
184
+ * Multivariate
185
+ * Vine Copula (Regular, Centered, Clustered, Conditional Sampling)
186
+
169
187
  * Prior Estimator:
170
188
  * Empirical
171
189
  * Black & Litterman
172
190
  * Factor Model
191
+ * Synthetic Data (Stress Test, Factor Stress Test)
173
192
 
174
193
  * Uncertainty Set Estimator:
175
194
  * On Expected Returns:
@@ -177,12 +196,14 @@ Available models
177
196
  * Circular Bootstrap
178
197
  * On Covariance:
179
198
  * Empirical
180
- * Circular bootstrap
199
+ * Circular Bootstrap
181
200
 
182
201
  * Pre-Selection Transformer:
183
202
  * Non-Dominated Selection
184
203
  * Select K Extremes (Best or Worst)
185
204
  * Drop Highly Correlated Assets
205
+ * Select Non-Expiring Assets
206
+ * Select Complete Assets (handle late inception, delisting, etc.)
186
207
 
187
208
  * Cross-Validation and Model Selection:
188
209
  * Compatible with all `sklearn` methods (KFold, etc.)
@@ -227,6 +248,8 @@ Available models
227
248
  * Budget Constraints
228
249
  * Tracking Error Constraints
229
250
  * Turnover Constraints
251
+ * Cardinality and Group Cardinality Constraints
252
+ * Threshold (Long and Short) Constraints
230
253
 
231
254
  Quickstart
232
255
  ~~~~~~~~~~
@@ -249,6 +272,7 @@ Imports
249
272
 
250
273
  from skfolio import RatioMeasure, RiskMeasure
251
274
  from skfolio.datasets import load_factors_dataset, load_sp500_dataset
275
+ from skfolio.distribution import VineCopula
252
276
  from skfolio.model_selection import (
253
277
  CombinatorialPurgedCV,
254
278
  WalkForward,
@@ -269,7 +293,7 @@ Imports
269
293
  )
270
294
  from skfolio.pre_selection import SelectKExtremes
271
295
  from skfolio.preprocessing import prices_to_returns
272
- from skfolio.prior import BlackLitterman, EmpiricalPrior, FactorModel
296
+ from skfolio.prior import BlackLitterman, EmpiricalPrior, FactorModel, SyntheticData
273
297
  from skfolio.uncertainty_set import BootstrapMuUncertaintySet
274
298
 
275
299
  Load Dataset
@@ -540,12 +564,61 @@ Combinatorial Purged Cross-Validation
540
564
  print(population.summary())
541
565
 
542
566
 
567
+ Minimum CVaR Optimization on Synthetic Returns
568
+ ----------------------------------------------
569
+ .. code-block:: python
570
+
571
+ vine = VineCopula(log_transform=True, n_jobs=-1)
572
+ prior = =SyntheticData(distribution_estimator=vine, n_samples=2000)
573
+ model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=prior)
574
+ model.fit(X)
575
+ print(model.weights_)
576
+
577
+
578
+ Stress Test
579
+ -----------
580
+ .. code-block:: python
581
+
582
+ vine = VineCopula(log_transform=True, central_assets=["BAC"] n_jobs=-1)
583
+ vine.fit(X)
584
+ X_stressed = vine.sample(n_samples=10_000, conditioning = {"BAC": -0.2})
585
+ ptf_stressed = model.predict(X_stressed)
586
+
587
+
588
+ Minimum CVaR Optimization on Synthetic Factors
589
+ ----------------------------------------------
590
+ .. code-block:: python
591
+
592
+ vine = VineCopula(central_assets=["QUAL"], log_transform=True, n_jobs=-1)
593
+ factor_prior = SyntheticData(
594
+ distribution_estimator=vine,
595
+ n_samples=10_000,
596
+ sample_args=dict(conditioning={"QUAL": -0.2}),
597
+ )
598
+ factor_model = FactorModel(factor_prior_estimator=factor_prior)
599
+ model = MeanRisk(risk_measure=RiskMeasure.CVAR, prior_estimator=factor_model)
600
+ model.fit(X, y)
601
+ print(model.weights_)
602
+
603
+
604
+ Factor Stress Test
605
+ ------------------
606
+ .. code-block:: python
607
+
608
+ factor_model.set_params(factor_prior_estimator__sample_args=dict(
609
+ conditioning={"QUAL": -0.5}
610
+ ))
611
+ factor_model.fit(X,y)
612
+ stressed_X = factor_model.prior_model_.returns
613
+ stressed_ptf = model.predict(stressed_X)
614
+
615
+
543
616
  Recognition
544
617
  ~~~~~~~~~~~
545
618
 
546
- We would like to thank all contributors behind our direct dependencies, such as
547
- scikit-learn and cvxpy, but also the contributors of the following resources that were a
548
- source of inspiration:
619
+ We would like to thank all contributors to our direct dependencies, such as
620
+ scikit-learn and cvxpy, as well as the contributors of the following resources that
621
+ served as sources of inspiration:
549
622
 
550
623
  * PyPortfolioOpt
551
624
  * Riskfolio-Lib
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "skfolio"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  maintainers = [
9
9
  { name = "Hugo Delatte", email = "delatte.hugo@gmail.com" },
10
10
  { name = "Matteo Manzi", email = "matteomanzi09@gmail.com" }
@@ -51,9 +51,10 @@ classifiers = [
51
51
  requires-python = ">=3.10"
52
52
  dependencies = [
53
53
  "numpy>=1.23.4",
54
- "scipy>=1.8.0",
54
+ "scipy>=1.15.2",
55
55
  "pandas>=1.4.1",
56
- "cvxpy>=1.4.1",
56
+ "cvxpy-base>=1.5.0",
57
+ "clarabel>=0.9.0",
57
58
  "scikit-learn>=1.6.0",
58
59
  "joblib>=1.3.2",
59
60
  "plotly>=5.22.0"
@@ -67,14 +68,14 @@ Repository = "https://github.com/skfolio/skfolio"
67
68
 
68
69
  [project.optional-dependencies]
69
70
  dev = [
70
- "cvxpy[SCIP]>=1.6.0,<2.0.0",
71
- "pytest>=8.3.4,<9.0.0",
72
- "pytest-cov>=6.0.0,<7.0.0",
73
- "ruff>=0.8.4,<1.0.0",
74
- "pre-commit>=4.0.0,<4.2.0",
71
+ "PySCIPOpt",
72
+ "pytest",
73
+ "pytest-cov",
74
+ "ruff",
75
+ "pre-commit",
75
76
  ]
76
77
  docs = [
77
- "cvxpy[SCIP]",
78
+ "PySCIPOpt",
78
79
  "Sphinx",
79
80
  "sphinx-gallery",
80
81
  "sphinx-design",
@@ -129,8 +130,12 @@ select = [
129
130
  "ISC", # implicit string concatenation
130
131
  "UP", # alert you when better syntax is available in your python version
131
132
  "RUF", # the ruff developer's own rules
133
+ "D",
132
134
  ]
133
- ignore = ["E203", "ISC001", "ISC002", "E111", "E114", "E117"] # rules redundant with the formatter.
135
+ ignore = ["E203", "ISC001", "ISC002", "E111", "E114", "E117", "A005", "D401", "D205"]
136
+
137
+ [tool.ruff.lint.per-file-ignores]
138
+ "tests/*" = ["D"]
134
139
 
135
140
  [tool.ruff.lint.isort]
136
141
  case-sensitive = true
@@ -138,6 +143,9 @@ case-sensitive = true
138
143
  [tool.ruff.lint.pycodestyle]
139
144
  max-line-length = 320
140
145
 
146
+ [tool.ruff.lint.pydocstyle]
147
+ convention = "numpy"
148
+
141
149
  [tool.pytest.ini_options]
142
150
  addopts = [
143
151
  "--import-mode=importlib",
@@ -1,7 +1,7 @@
1
- """skfolio package"""
1
+ """skfolio package."""
2
2
 
3
3
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
4
- # License: BSD 3 clause
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
5
  import importlib.metadata
6
6
 
7
7
  from skfolio.measures import (
@@ -1,7 +1,7 @@
1
1
  """Hierarchical Clustering estimators."""
2
2
 
3
3
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
4
- # License: BSD 3 clause
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
5
 
6
6
  from skfolio.cluster._hierarchical import HierarchicalClustering, LinkageMethod
7
7
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
 
7
7
  from enum import auto
8
8
 
@@ -1,7 +1,7 @@
1
1
  """Datasets module."""
2
2
 
3
3
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
4
- # License: BSD 3 clause
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
5
 
6
6
  from skfolio.datasets._base import (
7
7
  load_factors_dataset,
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # Implementation derived from:
7
7
  # scikit-portfolio, Copyright (c) 2022, Carlo Nicolini, Licensed under MIT Licence.
8
8
  # scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
@@ -74,7 +74,7 @@ def load_gzip_compressed_csv_data(
74
74
  encoding="utf-8",
75
75
  datetime_index: bool = True,
76
76
  ) -> pd.DataFrame:
77
- """Loads gzip-compressed csv files with `importlib.resources`.
77
+ """Load gzip-compressed csv files with `importlib.resources`.
78
78
 
79
79
  1) Open resource file with `importlib.resources.open_binary`
80
80
  2) Decompress csv file with `gzip.open`
@@ -0,0 +1 @@
1
+ """Dataset Data module."""
@@ -1,7 +1,7 @@
1
1
  """Distance Estimators."""
2
2
 
3
3
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
4
- # License: BSD 3 clause
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
5
 
6
6
  from skfolio.distance._base import BaseDistance
7
7
  from skfolio.distance._distance import (
@@ -1,8 +1,8 @@
1
- """Base Distance Estimators"""
1
+ """Base Distance Estimators."""
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
 
7
7
  from abc import ABC, abstractmethod
8
8
 
@@ -1,8 +1,8 @@
1
- """Distance Estimators"""
1
+ """Distance Estimators."""
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
 
7
7
  import numpy as np
8
8
  import numpy.typing as npt
@@ -215,7 +215,7 @@ class SpearmanDistance(BaseDistance):
215
215
  self.power = power
216
216
 
217
217
  def fit(self, X: npt.ArrayLike, y=None) -> "SpearmanDistance":
218
- """Fit the Spearman Kendall estimator.
218
+ """Fit the Spearman estimator.
219
219
 
220
220
  Parameters
221
221
  ----------
@@ -384,7 +384,7 @@ class DistanceCorrelation(BaseDistance):
384
384
 
385
385
  @staticmethod
386
386
  def _dcorr(x: np.ndarray, y: np.ndarray):
387
- """Calculate the distance correlation between two variables"""
387
+ """Calculate the distance correlation between two variables."""
388
388
  x = scd.squareform(scd.pdist(x.reshape(-1, 1)))
389
389
  y = scd.squareform(scd.pdist(y.reshape(-1, 1)))
390
390
  x = x - x.mean(axis=0)[np.newaxis, :] - x.mean(axis=1)[:, np.newaxis] + x.mean()
@@ -0,0 +1,56 @@
1
+ """Distribution module."""
2
+
3
+ from skfolio.distribution._base import BaseDistribution, SelectionCriterion
4
+ from skfolio.distribution.copula import (
5
+ BaseBivariateCopula,
6
+ ClaytonCopula,
7
+ CopulaRotation,
8
+ GaussianCopula,
9
+ GumbelCopula,
10
+ IndependentCopula,
11
+ JoeCopula,
12
+ StudentTCopula,
13
+ compute_pseudo_observations,
14
+ empirical_tail_concentration,
15
+ plot_tail_concentration,
16
+ select_bivariate_copula,
17
+ )
18
+ from skfolio.distribution.multivariate import (
19
+ BaseMultivariateDist,
20
+ DependenceMethod,
21
+ VineCopula,
22
+ )
23
+ from skfolio.distribution.univariate import (
24
+ BaseUnivariateDist,
25
+ Gaussian,
26
+ JohnsonSU,
27
+ NormalInverseGaussian,
28
+ StudentT,
29
+ select_univariate_dist,
30
+ )
31
+
32
+ __all__ = [
33
+ "BaseBivariateCopula",
34
+ "BaseDistribution",
35
+ "BaseMultivariateDist",
36
+ "BaseUnivariateDist",
37
+ "ClaytonCopula",
38
+ "CopulaRotation",
39
+ "DependenceMethod",
40
+ "Gaussian",
41
+ "GaussianCopula",
42
+ "GumbelCopula",
43
+ "IndependentCopula",
44
+ "JoeCopula",
45
+ "JohnsonSU",
46
+ "NormalInverseGaussian",
47
+ "SelectionCriterion",
48
+ "StudentT",
49
+ "StudentTCopula",
50
+ "VineCopula",
51
+ "compute_pseudo_observations",
52
+ "empirical_tail_concentration",
53
+ "plot_tail_concentration",
54
+ "select_bivariate_copula",
55
+ "select_univariate_dist",
56
+ ]