skfolio 0.8.1__py3-none-any.whl → 0.9.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.
@@ -256,9 +256,6 @@ class MaximumDiversification(MeanRisk):
256
256
  min_return : float | array-like of shape (n_optimization), optional
257
257
  Lower bound constraint on the expected return.
258
258
 
259
- min_return : float | array-like of shape (n_optimization), optional
260
- Lower bound constraint on the expected return.
261
-
262
259
  add_objective : Callable[[cp.Variable], cp.Expression], optional
263
260
  Add a custom objective to the existing objective expression.
264
261
  It is a function that must take as argument the weights `w` and returns a
@@ -902,7 +902,7 @@ class MeanRisk(ConvexOptimization):
902
902
  )
903
903
  constraints += [
904
904
  tracking_error * self._scale_constraints
905
- <= self.max_tracking_error * self._scale_constraints
905
+ <= self.max_tracking_error * factor * self._scale_constraints
906
906
  ]
907
907
 
908
908
  # Turnover
@@ -1036,6 +1036,15 @@ class MeanRisk(ConvexOptimization):
1036
1036
  + custom_objective * self._scale_objective
1037
1037
  )
1038
1038
  case ObjectiveFunction.MAXIMIZE_RATIO:
1039
+ # Capture common obvious mistake before solver failure to help user
1040
+ if np.isscalar(self.min_weights) and self.min_weights >= 0:
1041
+ if np.max(prior_model.mu) - self.risk_free_rate <= 0:
1042
+ raise ValueError(
1043
+ "Cannot optimize for Maximum Ratio with your current "
1044
+ "constraints and input. This is because your assets' "
1045
+ "expected returns are all under-performing your risk-free "
1046
+ f"rate {self.risk_free_rate:.2%}."
1047
+ )
1039
1048
  homogenization_factor = _optimal_homogenization_factor(
1040
1049
  mu=prior_model.mu
1041
1050
  )
@@ -1060,7 +1069,6 @@ class MeanRisk(ConvexOptimization):
1060
1069
  # Fractional Programming Problems".
1061
1070
  # The condition to work is f1 >= 0, so we need to raise an user
1062
1071
  # warning when it's not the case.
1063
- # TODO: raise user warning when f1<0
1064
1072
 
1065
1073
  constraints += [
1066
1074
  expected_return * self._scale_constraints
@@ -1,6 +1,7 @@
1
1
  """Pre Selection module."""
2
2
 
3
3
  from skfolio.pre_selection._drop_correlated import DropCorrelated
4
+ from skfolio.pre_selection._drop_zero_variance import DropZeroVariance
4
5
  from skfolio.pre_selection._select_complete import SelectComplete
5
6
  from skfolio.pre_selection._select_k_extremes import SelectKExtremes
6
7
  from skfolio.pre_selection._select_non_dominated import SelectNonDominated
@@ -8,6 +9,7 @@ from skfolio.pre_selection._select_non_expiring import SelectNonExpiring
8
9
 
9
10
  __all__ = [
10
11
  "DropCorrelated",
12
+ "DropZeroVariance",
11
13
  "SelectComplete",
12
14
  "SelectKExtremes",
13
15
  "SelectNonDominated",
@@ -0,0 +1,75 @@
1
+ """Pre-selection DropZeroVariance module."""
2
+
3
+ # Copyright (c) 2025
4
+ # Author: Vincent Maladiere <maladiere.vincent@gmail.com>
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
+
7
+ import numpy as np
8
+ import numpy.typing as npt
9
+ import sklearn.base as skb
10
+ import sklearn.feature_selection as skf
11
+ import sklearn.utils.validation as skv
12
+
13
+
14
+ class DropZeroVariance(skf.SelectorMixin, skb.BaseEstimator):
15
+ """Transformer for dropping assets with near-zero variance.
16
+
17
+ On short windows, some assets can experience a near-zero variance, making
18
+ the covariance matrix improper for optimization. This simple transformer drops
19
+ assets whose variance is below some threshold.
20
+
21
+ Parameters
22
+ ----------
23
+ threshold : float, default=1e-8
24
+ Minimum variance threshold. The default value is 1e-8. For daily asset returns,
25
+ this value filters out assets whose daily standard deviation is below 1e-4
26
+ (0.01%), which corresponds to an annual standard deviation of approximately
27
+ 0.16%, assuming 252 trading days.
28
+
29
+ Attributes
30
+ ----------
31
+ to_keep_ : ndarray of shape (n_assets, )
32
+ Boolean array indicating which assets are remaining.
33
+
34
+ n_features_in_ : int
35
+ Number of assets seen during `fit`.
36
+
37
+ feature_names_in_ : ndarray of shape (`n_features_in_`,)
38
+ Names of assets seen during `fit`. Defined only when `X`
39
+ has assets names that are all strings.
40
+ """
41
+
42
+ to_keep_: np.ndarray
43
+
44
+ def __init__(self, threshold: float = 1e-8):
45
+ self.threshold = threshold
46
+
47
+ def fit(self, X: npt.ArrayLike, y=None):
48
+ """Fit the transformer on some assets.
49
+
50
+ Parameters
51
+ ----------
52
+ X : array-like of shape (n_observations, n_assets)
53
+ Price returns of the assets.
54
+
55
+ y : Ignored
56
+ Not used, present for API consistency by convention.
57
+
58
+ Returns
59
+ -------
60
+ self : DropZeroVariance
61
+ Fitted estimator.
62
+ """
63
+ X = skv.validate_data(self, X)
64
+ if self.threshold < 0:
65
+ raise ValueError(
66
+ f"`threshold` must be higher than 0, got {self.threshold}."
67
+ )
68
+
69
+ self.to_keep_ = X.var(axis=0) > self.threshold
70
+
71
+ return self
72
+
73
+ def _get_support_mask(self):
74
+ skv.check_is_fitted(self)
75
+ return self.to_keep_
@@ -123,7 +123,7 @@ class SelectNonExpiring(skf.SelectorMixin, skb.BaseEstimator):
123
123
  )
124
124
 
125
125
  if self.expiration_dates is None:
126
- raise ValueError("`expiration_lookahead` must be provided")
126
+ raise ValueError("`expiration_dates` must be provided")
127
127
 
128
128
  if self.expiration_lookahead is None:
129
129
  raise ValueError("`expiration_lookahead` must be provided")
@@ -276,7 +276,7 @@ def _matching_array(values: np.ndarray, key: str, sum_to_one: bool) -> np.ndarra
276
276
  if not arr.any():
277
277
  raise EquationToMatrixError(f"Unable to find '{key}' in '{values}'")
278
278
  if sum_to_one:
279
- s = np.sum(arr)
279
+ s = arr.sum()
280
280
  else:
281
281
  s = 1
282
282
  return arr / s
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skfolio
3
- Version: 0.8.1
3
+ Version: 0.9.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>
@@ -74,8 +74,8 @@ skfolio/optimization/cluster/hierarchical/_hrp.py,sha256=06kch9QkthV5yB8RNB_5Xz-
74
74
  skfolio/optimization/convex/__init__.py,sha256=q1Q2p7HcnmbQlBIA0SXm0TwDGxl7hRc0JhF1o01lFSg,605
75
75
  skfolio/optimization/convex/_base.py,sha256=mUTXVM6bq5cvlieAl6TXNGd6BNIqBajoAiDL28fPx9o,89455
76
76
  skfolio/optimization/convex/_distributionally_robust.py,sha256=4iWfEuJGuBawVGU5X1-QHVMMh9hBnMtou2Uh5hRdXeA,17958
77
- skfolio/optimization/convex/_maximum_diversification.py,sha256=a8nDecN6jTR_bOFKBsenI4G2kNu5t98y5ALY78lNrAU,19657
78
- skfolio/optimization/convex/_mean_risk.py,sha256=mM5KMCxwAf1dT6JTxJuuQfvvk63hMQg5GD3LumQkfjQ,49534
77
+ skfolio/optimization/convex/_maximum_diversification.py,sha256=hYnzmHjVrc0gGIrGsMuc0ptGqKJNSU5t16DIjSErYHM,19529
78
+ skfolio/optimization/convex/_mean_risk.py,sha256=Puj-OUBwV-0y4vbNwwNVKTQqf1YuwQxqf8pknOTZ3ec,50105
79
79
  skfolio/optimization/convex/_risk_budgeting.py,sha256=xtRg3CGmasi-ebx7e5XevHJs3n9PpccaZR7Z7loyKDc,23653
80
80
  skfolio/optimization/ensemble/__init__.py,sha256=IJhsX8f-6wclc9a6Fd8yAQvZKxtxq4Qf7AC2CLryHrU,195
81
81
  skfolio/optimization/ensemble/_base.py,sha256=e0dWCEIYnho3HU2KGGS9UHQdycdVuqMcTe7hi0LihjQ,3416
@@ -88,12 +88,13 @@ skfolio/portfolio/__init__.py,sha256=YeDSH0ZdyE-lcbDqpNw9IOltURtoM-ewAzzcec44Q5Q
88
88
  skfolio/portfolio/_base.py,sha256=V81HUQ2CWmohGOeNip1dPESGnmRKQk8eDAthjkvVFhQ,40541
89
89
  skfolio/portfolio/_multi_period_portfolio.py,sha256=9z71aZL2GrV6rQ_EkIyPkK-mJ9N2ZLZCIinSScfRgfw,24412
90
90
  skfolio/portfolio/_portfolio.py,sha256=o1e1KNZAuxlC8y3zTIcaW7c2jk_LlEBCzEF8FRJht20,32791
91
- skfolio/pre_selection/__init__.py,sha256=3hqxwd8nAa1dBna5MrE1P5JPrM-OkSvXGyhbMq7ZKIk,511
91
+ skfolio/pre_selection/__init__.py,sha256=6J_D0QIMi24owwJJP6vxYnIgIyWZuMzCMnpMCEpAvCo,606
92
92
  skfolio/pre_selection/_drop_correlated.py,sha256=4-PSd8R20Rcdyc8Zzcy9B2eRPEtaEkM3YXi74YKF-Pk,3839
93
+ skfolio/pre_selection/_drop_zero_variance.py,sha256=66Mi0Fta1kdmLw0CCqa7p9AqpoBpS9B3fGPLqhb8VIU,2312
93
94
  skfolio/pre_selection/_select_complete.py,sha256=2nEvcjROMJzhAHMCHADeAiCws_tc-BMtndIkjRexL84,3902
94
95
  skfolio/pre_selection/_select_k_extremes.py,sha256=nMugK88igmscribCw_I1UnjE_O7cuIjrJF8AGuVTfiA,3082
95
96
  skfolio/pre_selection/_select_non_dominated.py,sha256=Auv7G8E1QNO96heb35oBWmFLd68LlVDRgSpcg7wpv5A,6004
96
- skfolio/pre_selection/_select_non_expiring.py,sha256=hVXLNw5KBU7WxOI6v4feZ9lJaVIgl-CBhW80T9-ZUac,5105
97
+ skfolio/pre_selection/_select_non_expiring.py,sha256=g7ekl69MxQFkg06Km3NiuJXr0JfYPqvo5hMLQsIcUKY,5101
97
98
  skfolio/preprocessing/__init__.py,sha256=94jMyP_E7FlwQVE8D_bXDi8KyfAA2xPHTDvYOi6zf_g,123
98
99
  skfolio/preprocessing/_returns.py,sha256=6G5qJIVHGnIoeBNAqpJTB-569g9NeXVIyrz033bK5Gk,4576
99
100
  skfolio/prior/__init__.py,sha256=daUO3ha87Nu0ixJci33dR1dKgoYC6-1Nf3AUoaskE5o,544
@@ -109,12 +110,12 @@ skfolio/uncertainty_set/_bootstrap.py,sha256=tDnUvhTtl0HWu-xL6MWZZZyWs4Y06PKQ5xP
109
110
  skfolio/uncertainty_set/_empirical.py,sha256=t9_V23gH1eJ0jaASQcus-QOSATAr9HKVW2hjHMNYjO0,9380
110
111
  skfolio/utils/__init__.py,sha256=bC6-MsCVF7xKTr48z7OzJJUeWvqAB7BiHeNTiKsme70,20
111
112
  skfolio/utils/bootstrap.py,sha256=6BN_9CgfbeImBSNEE0dF52FRGuQT41HcQXeHPLwFqJc,3565
112
- skfolio/utils/equations.py,sha256=yj6-TReoPq3YaUQyAx-t4wZNbODON2T4TyA82z2SnkU,15577
113
+ skfolio/utils/equations.py,sha256=ZDEAapAfWeiOHvQbboswSqfQInlQklKJDtkiV__d-sc,15575
113
114
  skfolio/utils/sorting.py,sha256=F7gfIBfnulfDUiqvzrlR-pba4PPLJT6NH7-5s4sdRhw,3521
114
115
  skfolio/utils/stats.py,sha256=glVHo7rjwy06dl5kkULLOADMrEkVJcfXXAz-1qmYQL4,17005
115
116
  skfolio/utils/tools.py,sha256=sGJFiqc60TqXyaWoH7JdsbaFYj_bvwq3hHIk6FxDC3U,22994
116
- skfolio-0.8.1.dist-info/licenses/LICENSE,sha256=F6Gi-ZJX5BlVzYK8R9NcvAkAsKa7KO29xB1OScbrH6Q,1526
117
- skfolio-0.8.1.dist-info/METADATA,sha256=OkbDH8ZCJ72VOXzIi4KEm1ZwGQ4E05WVMCkWGg6EqYk,22383
118
- skfolio-0.8.1.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
119
- skfolio-0.8.1.dist-info/top_level.txt,sha256=NXEaoS9Ms7t32gxkb867nV0OKlU0KmssL7IJBVo0fJs,8
120
- skfolio-0.8.1.dist-info/RECORD,,
117
+ skfolio-0.9.1.dist-info/licenses/LICENSE,sha256=F6Gi-ZJX5BlVzYK8R9NcvAkAsKa7KO29xB1OScbrH6Q,1526
118
+ skfolio-0.9.1.dist-info/METADATA,sha256=139u03mcy89z6d922cjhRPNqr-Buh1NT4TIvPFX4sB0,22383
119
+ skfolio-0.9.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
120
+ skfolio-0.9.1.dist-info/top_level.txt,sha256=NXEaoS9Ms7t32gxkb867nV0OKlU0KmssL7IJBVo0fJs,8
121
+ skfolio-0.9.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5