skfolio 0.1.2__tar.gz → 0.2.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.
- {skfolio-0.1.2/src/skfolio.egg-info → skfolio-0.2.0}/PKG-INFO +20 -21
- {skfolio-0.1.2 → skfolio-0.2.0}/README.rst +19 -19
- {skfolio-0.1.2 → skfolio-0.2.0}/pyproject.toml +10 -13
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/datasets/_base.py +4 -1
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/distance/_distance.py +0 -1
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/measures/_enums.py +3 -1
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/measures/_measures.py +0 -1
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/model_selection/__init__.py +2 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/model_selection/_combinatorial.py +172 -21
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/model_selection/_validation.py +8 -6
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/_base.py +19 -12
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/cluster/hierarchical/_base.py +5 -4
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/cluster/hierarchical/_herc.py +2 -2
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/cluster/hierarchical/_hrp.py +2 -3
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/convex/_base.py +5 -4
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/convex/_distributionally_robust.py +2 -2
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/convex/_maximum_diversification.py +2 -2
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/convex/_mean_risk.py +2 -2
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/convex/_risk_budgeting.py +2 -2
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/ensemble/_base.py +1 -3
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/ensemble/_stacking.py +6 -4
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/naive/_naive.py +6 -6
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/population/_population.py +48 -39
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/portfolio/_base.py +0 -1
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/portfolio/_portfolio.py +13 -12
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/prior/_factor_model.py +8 -5
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/fixes/_dendrogram.py +9 -7
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/tools.py +8 -7
- {skfolio-0.1.2 → skfolio-0.2.0/src/skfolio.egg-info}/PKG-INFO +20 -21
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio.egg-info/requires.txt +0 -1
- {skfolio-0.1.2 → skfolio-0.2.0}/LICENSE +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/MANIFEST.in +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/setup.cfg +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/cluster/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/cluster/_hierarchical.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/datasets/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/datasets/data/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/datasets/data/factors_dataset.csv.gz +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/datasets/data/sp500_dataset.csv.gz +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/datasets/data/sp500_index.csv.gz +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/distance/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/distance/_base.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/exceptions.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/measures/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/metrics/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/metrics/_scorer.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/model_selection/_walk_forward.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/moments/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/moments/covariance/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/moments/covariance/_base.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/moments/covariance/_covariance.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/moments/expected_returns/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/moments/expected_returns/_base.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/moments/expected_returns/_expected_returns.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/cluster/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/cluster/_nco.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/cluster/hierarchical/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/convex/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/ensemble/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/optimization/naive/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/population/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/portfolio/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/portfolio/_multi_period_portfolio.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/pre_selection/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/pre_selection/_pre_selection.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/preprocessing/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/preprocessing/_returns.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/prior/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/prior/_base.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/prior/_black_litterman.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/prior/_empirical.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/typing.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/uncertainty_set/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/uncertainty_set/_base.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/uncertainty_set/_bootstrap.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/uncertainty_set/_empirical.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/bootstrap.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/equations.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/fixes/__init__.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/sorting.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio/utils/stats.py +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio.egg-info/SOURCES.txt +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio.egg-info/dependency_links.txt +0 -0
- {skfolio-0.1.2 → skfolio-0.2.0}/src/skfolio.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: skfolio
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.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>
|
@@ -66,7 +66,6 @@ Requires-Dist: plotly>=5.15.0
|
|
66
66
|
Provides-Extra: tests
|
67
67
|
Requires-Dist: pytest; extra == "tests"
|
68
68
|
Requires-Dist: pytest-cov; extra == "tests"
|
69
|
-
Requires-Dist: black; extra == "tests"
|
70
69
|
Requires-Dist: ruff; extra == "tests"
|
71
70
|
Provides-Extra: docs
|
72
71
|
Requires-Dist: Sphinx; extra == "docs"
|
@@ -86,37 +85,37 @@ Requires-Dist: sphinx-favicon; extra == "docs"
|
|
86
85
|
|
87
86
|
.. -*- mode: rst -*-
|
88
87
|
|
89
|
-
|Licence|
|
88
|
+
|Licence| |Codecov| |Black| |PythonVersion| |PyPi| |CI/CD| |Downloads| |Ruff| |Contribution| |Website|
|
90
89
|
|
91
90
|
.. |Licence| image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
|
92
|
-
|
91
|
+
:target: https://github.com/skfolio/skfolio/blob/main/LICENSE
|
93
92
|
|
94
93
|
.. |Codecov| image:: https://codecov.io/gh/skfolio/skfolio/graph/badge.svg?token=KJ0SE4LHPV
|
95
|
-
|
94
|
+
:target: https://codecov.io/gh/skfolio/skfolio
|
96
95
|
|
97
|
-
.. |PythonVersion| image:: https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue
|
98
|
-
|
96
|
+
.. |PythonVersion| image:: https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue.svg
|
97
|
+
:target: https://pypi.org/project/skfolio/
|
99
98
|
|
100
99
|
.. |PyPi| image:: https://img.shields.io/pypi/v/skfolio
|
101
|
-
|
100
|
+
:target: https://pypi.org/project/skfolio
|
102
101
|
|
103
102
|
.. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
104
|
-
|
103
|
+
:target: https://github.com/psf/black
|
105
104
|
|
106
|
-
.. |CI/CD| image:: https://img.shields.io/github/actions/workflow/status/skfolio/skfolio/release.yml?logo=github
|
107
|
-
|
105
|
+
.. |CI/CD| image:: https://img.shields.io/github/actions/workflow/status/skfolio/skfolio/release.yml.svg?logo=github
|
106
|
+
:target: https://github.com/skfolio/skfolio/raw/main/LICENSE
|
108
107
|
|
109
108
|
.. |Downloads| image:: https://static.pepy.tech/badge/skfolio
|
110
|
-
|
109
|
+
:target: https://pepy.tech/project/skfolio
|
111
110
|
|
112
111
|
.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
113
|
-
|
112
|
+
:target: https://github.com/astral-sh/ruff
|
114
113
|
|
115
114
|
.. |Contribution| image:: https://img.shields.io/badge/Contributions-Welcome-blue
|
116
|
-
|
115
|
+
:target: https://github.com/skfolio/skfolio/blob/main/CONTRIBUTING.md
|
117
116
|
|
118
|
-
.. |Website| image:: https://img.shields.io/website
|
119
|
-
|
117
|
+
.. |Website| image:: https://img.shields.io/website.svg?down_color=red&down_message=down&up_color=53cc0d&up_message=up&url=https://skfolio.org
|
118
|
+
:target: https://skfolio.org
|
120
119
|
|
121
120
|
.. |PythonMinVersion| replace:: 3.10
|
122
121
|
.. |NumpyMinVersion| replace:: 1.23.4
|
@@ -646,9 +645,9 @@ If you use `skfolio` in a scientific publication, we would appreciate citations:
|
|
646
645
|
Bibtex entry::
|
647
646
|
|
648
647
|
@misc{skfolio,
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
648
|
+
author = {Delatte, Hugo and Nicolini, Carlo},
|
649
|
+
title = {skfolio},
|
650
|
+
year = {2023},
|
651
|
+
url = {https://github.com/skfolio/skfolio}
|
652
|
+
}
|
654
653
|
|
@@ -1,36 +1,36 @@
|
|
1
1
|
.. -*- mode: rst -*-
|
2
2
|
|
3
|
-
|Licence|
|
3
|
+
|Licence| |Codecov| |Black| |PythonVersion| |PyPi| |CI/CD| |Downloads| |Ruff| |Contribution| |Website|
|
4
4
|
|
5
5
|
.. |Licence| image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
|
6
|
-
|
6
|
+
:target: https://github.com/skfolio/skfolio/blob/main/LICENSE
|
7
7
|
|
8
8
|
.. |Codecov| image:: https://codecov.io/gh/skfolio/skfolio/graph/badge.svg?token=KJ0SE4LHPV
|
9
|
-
|
9
|
+
:target: https://codecov.io/gh/skfolio/skfolio
|
10
10
|
|
11
|
-
.. |PythonVersion| image:: https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue
|
12
|
-
|
11
|
+
.. |PythonVersion| image:: https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue.svg
|
12
|
+
:target: https://pypi.org/project/skfolio/
|
13
13
|
|
14
14
|
.. |PyPi| image:: https://img.shields.io/pypi/v/skfolio
|
15
|
-
|
15
|
+
:target: https://pypi.org/project/skfolio
|
16
16
|
|
17
17
|
.. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
18
|
-
|
18
|
+
:target: https://github.com/psf/black
|
19
19
|
|
20
|
-
.. |CI/CD| image:: https://img.shields.io/github/actions/workflow/status/skfolio/skfolio/release.yml?logo=github
|
21
|
-
|
20
|
+
.. |CI/CD| image:: https://img.shields.io/github/actions/workflow/status/skfolio/skfolio/release.yml.svg?logo=github
|
21
|
+
:target: https://github.com/skfolio/skfolio/raw/main/LICENSE
|
22
22
|
|
23
23
|
.. |Downloads| image:: https://static.pepy.tech/badge/skfolio
|
24
|
-
|
24
|
+
:target: https://pepy.tech/project/skfolio
|
25
25
|
|
26
26
|
.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
27
|
-
|
27
|
+
:target: https://github.com/astral-sh/ruff
|
28
28
|
|
29
29
|
.. |Contribution| image:: https://img.shields.io/badge/Contributions-Welcome-blue
|
30
|
-
|
30
|
+
:target: https://github.com/skfolio/skfolio/blob/main/CONTRIBUTING.md
|
31
31
|
|
32
|
-
.. |Website| image:: https://img.shields.io/website
|
33
|
-
|
32
|
+
.. |Website| image:: https://img.shields.io/website.svg?down_color=red&down_message=down&up_color=53cc0d&up_message=up&url=https://skfolio.org
|
33
|
+
:target: https://skfolio.org
|
34
34
|
|
35
35
|
.. |PythonMinVersion| replace:: 3.10
|
36
36
|
.. |NumpyMinVersion| replace:: 1.23.4
|
@@ -560,9 +560,9 @@ If you use `skfolio` in a scientific publication, we would appreciate citations:
|
|
560
560
|
Bibtex entry::
|
561
561
|
|
562
562
|
@misc{skfolio,
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
563
|
+
author = {Delatte, Hugo and Nicolini, Carlo},
|
564
|
+
title = {skfolio},
|
565
|
+
year = {2023},
|
566
|
+
url = {https://github.com/skfolio/skfolio}
|
567
|
+
}
|
568
568
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "skfolio"
|
7
|
-
version = "0.
|
7
|
+
version = "0.2.0"
|
8
8
|
maintainers = [
|
9
9
|
{ name = "Hugo Delatte", email = "delatte.hugo@gmail.com" },
|
10
10
|
]
|
@@ -62,7 +62,6 @@ classifiers = [
|
|
62
62
|
tests = [
|
63
63
|
"pytest",
|
64
64
|
"pytest-cov",
|
65
|
-
"black",
|
66
65
|
"ruff"
|
67
66
|
]
|
68
67
|
docs = [
|
@@ -106,33 +105,31 @@ commit_message = "v{version} [skip ci]\n\nAutomatically generated by python-sema
|
|
106
105
|
[tool.semantic_release.remote]
|
107
106
|
token = { env = "GH_TOKEN" }
|
108
107
|
|
109
|
-
[tool.black]
|
110
|
-
line-length = 88
|
111
|
-
target_version = ["py310", "py311"]
|
112
|
-
preview = true
|
113
|
-
|
114
108
|
[tool.ruff]
|
115
109
|
include = ["pyproject.toml", "src/**/*.py"]
|
110
|
+
line-length = 88
|
111
|
+
src = ["src"]
|
112
|
+
target-version = "py310"
|
113
|
+
|
114
|
+
[tool.ruff.lint]
|
116
115
|
select = [
|
117
116
|
"E", # pycodestyle
|
118
117
|
"F", # pyflakes
|
119
118
|
"I", # isort
|
120
119
|
"A", # prevent using keywords that clobber python builtins
|
121
120
|
"B", # bugbear: security warnings
|
122
|
-
"E", # pycodestyle
|
123
121
|
"F", # pyflakes
|
124
122
|
"ISC", # implicit string concatenation
|
125
123
|
"UP", # alert you when better syntax is available in your python version
|
126
124
|
"RUF", # the ruff developer's own rules
|
127
125
|
]
|
128
|
-
ignore = ["E203", "
|
129
|
-
# Same as Black.
|
130
|
-
line-length = 88
|
131
|
-
target-version = "py310"
|
126
|
+
ignore = ["E203", "ISC001", "ISC002", "E111", "E114", "E117"] # rules redundant with the formatter.
|
132
127
|
|
133
|
-
[tool.ruff.isort]
|
128
|
+
[tool.ruff.lint.isort]
|
134
129
|
case-sensitive = true
|
135
130
|
|
131
|
+
[tool.ruff.lint.pycodestyle]
|
132
|
+
max-line-length = 320
|
136
133
|
|
137
134
|
[tool.pytest.ini_options]
|
138
135
|
addopts = [
|
@@ -140,7 +140,10 @@ def download_dataset(
|
|
140
140
|
DataFrame with each row representing one observation and each column
|
141
141
|
representing the asset price of a given observation.
|
142
142
|
"""
|
143
|
-
url =
|
143
|
+
url = (
|
144
|
+
f"https://github.com/skfolio/skfolio-datasets/raw/main/"
|
145
|
+
f"datasets/{data_filename}.csv.gz"
|
146
|
+
)
|
144
147
|
|
145
148
|
data_home = get_data_home(data_home=data_home)
|
146
149
|
filepath = os.path.join(data_home, f"{data_filename}.pkz")
|
@@ -6,6 +6,7 @@
|
|
6
6
|
from skfolio.model_selection._combinatorial import (
|
7
7
|
BaseCombinatorialCV,
|
8
8
|
CombinatorialPurgedCV,
|
9
|
+
optimal_folds_number,
|
9
10
|
)
|
10
11
|
from skfolio.model_selection._validation import cross_val_predict
|
11
12
|
from skfolio.model_selection._walk_forward import WalkForward
|
@@ -15,4 +16,5 @@ __all__ = [
|
|
15
16
|
"WalkForward",
|
16
17
|
"BaseCombinatorialCV",
|
17
18
|
"CombinatorialPurgedCV",
|
19
|
+
"optimal_folds_number",
|
18
20
|
]
|
@@ -197,19 +197,13 @@ class CombinatorialPurgedCV(BaseCombinatorialCV):
|
|
197
197
|
@property
|
198
198
|
def n_splits(self) -> int:
|
199
199
|
"""Number of splits"""
|
200
|
-
return
|
201
|
-
math.factorial(self.n_folds)
|
202
|
-
/ (
|
203
|
-
math.factorial(self.n_test_folds)
|
204
|
-
* math.factorial(self.n_folds - self.n_test_folds)
|
205
|
-
)
|
206
|
-
)
|
200
|
+
return _n_splits(n_folds=self.n_folds, n_test_folds=self.n_test_folds)
|
207
201
|
|
208
202
|
@property
|
209
203
|
def n_test_paths(self) -> int:
|
210
204
|
"""Number of test paths that can be reconstructed from the train/test
|
211
205
|
combinations"""
|
212
|
-
return self.
|
206
|
+
return _n_test_paths(n_folds=self.n_folds, n_test_folds=self.n_test_folds)
|
213
207
|
|
214
208
|
@property
|
215
209
|
def test_set_index(self) -> np.ndarray:
|
@@ -320,19 +314,24 @@ class CombinatorialPurgedCV(BaseCombinatorialCV):
|
|
320
314
|
yield train_index, test_index_list
|
321
315
|
|
322
316
|
def summary(self, X) -> pd.Series:
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
317
|
+
n_observations = X.shape[0]
|
318
|
+
avg_train_size = _avg_train_size(
|
319
|
+
n_observations=n_observations,
|
320
|
+
n_folds=self.n_folds,
|
321
|
+
n_test_folds=self.n_test_folds,
|
322
|
+
)
|
323
|
+
return pd.Series(
|
324
|
+
{
|
325
|
+
"Number of Observations": n_observations,
|
326
|
+
"Total Number of Folds": self.n_folds,
|
327
|
+
"Number of Test Folds": self.n_test_folds,
|
328
|
+
"Purge Size": self.purged_size,
|
329
|
+
"Embargo Size": self.embargo_size,
|
330
|
+
"Average Training Size": int(avg_train_size),
|
331
|
+
"Number of Test Paths": self.n_test_paths,
|
332
|
+
"Number of Training Combinations": self.n_splits,
|
333
|
+
}
|
334
|
+
)
|
336
335
|
|
337
336
|
def plot_train_test_folds(self) -> skt.Figure:
|
338
337
|
"""Plot the train/test fold locations"""
|
@@ -408,3 +407,155 @@ class CombinatorialPurgedCV(BaseCombinatorialCV):
|
|
408
407
|
)
|
409
408
|
|
410
409
|
return fig
|
410
|
+
|
411
|
+
|
412
|
+
def _n_splits(n_folds: int, n_test_folds: int) -> int:
|
413
|
+
"""Number of splits.
|
414
|
+
|
415
|
+
Parameters
|
416
|
+
----------
|
417
|
+
n_folds : int
|
418
|
+
Number of folds.
|
419
|
+
|
420
|
+
n_test_folds : int
|
421
|
+
Number of test folds.
|
422
|
+
|
423
|
+
Returns
|
424
|
+
-------
|
425
|
+
n_splits : int
|
426
|
+
Number of splits
|
427
|
+
"""
|
428
|
+
return int(math.comb(n_folds, n_test_folds))
|
429
|
+
|
430
|
+
|
431
|
+
def _n_test_paths(n_folds: int, n_test_folds: int) -> int:
|
432
|
+
"""Number of test paths that can be reconstructed from the train/test
|
433
|
+
combinations
|
434
|
+
|
435
|
+
Parameters
|
436
|
+
----------
|
437
|
+
n_folds : int
|
438
|
+
Number of folds.
|
439
|
+
|
440
|
+
n_test_folds : int
|
441
|
+
Number of test folds.
|
442
|
+
|
443
|
+
Returns
|
444
|
+
-------
|
445
|
+
n_splits : int
|
446
|
+
Number of test paths.
|
447
|
+
"""
|
448
|
+
return (
|
449
|
+
_n_splits(n_folds=n_folds, n_test_folds=n_test_folds) * n_test_folds // n_folds
|
450
|
+
)
|
451
|
+
|
452
|
+
|
453
|
+
def _avg_train_size(n_observations: int, n_folds: int, n_test_folds: int) -> float:
|
454
|
+
"""Average number of observations contained in each training set.
|
455
|
+
|
456
|
+
Parameters
|
457
|
+
----------
|
458
|
+
n_observations : int
|
459
|
+
Number of observations.
|
460
|
+
|
461
|
+
n_folds : int
|
462
|
+
Number of folds.
|
463
|
+
|
464
|
+
n_test_folds : int
|
465
|
+
Number of test folds.
|
466
|
+
|
467
|
+
Returns
|
468
|
+
-------
|
469
|
+
avg_train_size : float
|
470
|
+
Average number of observations contained in each training set.
|
471
|
+
"""
|
472
|
+
return n_observations / n_folds * (n_folds - n_test_folds)
|
473
|
+
|
474
|
+
|
475
|
+
def optimal_folds_number(
|
476
|
+
n_observations: int,
|
477
|
+
target_train_size: int,
|
478
|
+
target_n_test_paths: int,
|
479
|
+
weight_train_size: float = 1,
|
480
|
+
weight_n_test_paths: float = 1,
|
481
|
+
) -> tuple[int, int]:
|
482
|
+
r"""Find the optimal number of folds (total folds and test folds) for a target
|
483
|
+
training size and a target number of test paths.
|
484
|
+
|
485
|
+
We find `x = n_folds` and `y = n_test_folds` that minimizes the below
|
486
|
+
cost function of the relative distance from the two targets:
|
487
|
+
|
488
|
+
.. math::
|
489
|
+
cost(x,y) = w_{f} \times \lvert\frac{f(x,y)-f_{target}}{f_{target}}\rvert + w_{g} \times \lvert\frac{g(x,y)-g_{target}}{g_{target}}\rvert
|
490
|
+
|
491
|
+
with :math:`w_{f}` and :math:`w_{g}` the weights assigned to the distance
|
492
|
+
from each target and :math:`f(x,y)` and :math:`g(x,y)` the average training size
|
493
|
+
and the number of test paths as a function of the number of total folds and test
|
494
|
+
folds.
|
495
|
+
|
496
|
+
This is a combinatorial problem with :math:`\frac{T\times(T-3)}{2}` combinations,
|
497
|
+
with :math:`T` the number of observations.
|
498
|
+
|
499
|
+
We reduce the search space by using the combinatorial symetry
|
500
|
+
:math:`{n \choose k}={n \choose n-k}` and skipping cost computation above 1e5.
|
501
|
+
|
502
|
+
Parameters
|
503
|
+
----------
|
504
|
+
n_observations : int
|
505
|
+
Number of observations.
|
506
|
+
|
507
|
+
target_train_size : int
|
508
|
+
The target number of observation in the training set.
|
509
|
+
|
510
|
+
target_n_test_paths : int
|
511
|
+
The target number of test paths (that can be reconstructed from the train/test
|
512
|
+
combinations).
|
513
|
+
|
514
|
+
weight_train_size : float, default=1
|
515
|
+
The weight assigned to the distance from the target train size.
|
516
|
+
The default value is 1.
|
517
|
+
|
518
|
+
weight_n_test_paths : float, default=1
|
519
|
+
The weight assigned to the distance from the target number of test paths.
|
520
|
+
The default value is 1.
|
521
|
+
|
522
|
+
Returns
|
523
|
+
-------
|
524
|
+
n_folds : int
|
525
|
+
Optimal number of total folds.
|
526
|
+
|
527
|
+
n_test_folds : int
|
528
|
+
Optimal number of test folds.
|
529
|
+
"""
|
530
|
+
|
531
|
+
def _cost(
|
532
|
+
x: int,
|
533
|
+
y: int,
|
534
|
+
) -> float:
|
535
|
+
n_test_paths = _n_test_paths(n_folds=x, n_test_folds=y)
|
536
|
+
avg_train_size = _avg_train_size(
|
537
|
+
n_observations=n_observations, n_folds=x, n_test_folds=y
|
538
|
+
)
|
539
|
+
return (
|
540
|
+
weight_n_test_paths
|
541
|
+
* abs(n_test_paths - target_n_test_paths)
|
542
|
+
/ target_n_test_paths
|
543
|
+
+ weight_train_size
|
544
|
+
* abs(avg_train_size - target_train_size)
|
545
|
+
/ target_train_size
|
546
|
+
)
|
547
|
+
|
548
|
+
costs = []
|
549
|
+
res = []
|
550
|
+
for n_folds in range(3, n_observations + 1):
|
551
|
+
i = None
|
552
|
+
for n_test_folds in range(2, n_folds):
|
553
|
+
if i is None or n_folds - n_test_folds <= i:
|
554
|
+
cost = _cost(x=n_folds, y=n_test_folds)
|
555
|
+
costs.append(cost)
|
556
|
+
res.append((n_folds, n_test_folds))
|
557
|
+
if i is None and cost > 1e5:
|
558
|
+
i = n_test_folds
|
559
|
+
|
560
|
+
j = np.argmin(costs)
|
561
|
+
return res[j]
|
@@ -170,12 +170,14 @@ def cross_val_predict(
|
|
170
170
|
path_id = path_ids[i, j]
|
171
171
|
portfolios[path_id].append(p)
|
172
172
|
name = portfolio_params.pop("name", "path")
|
173
|
-
pred = Population(
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
173
|
+
pred = Population(
|
174
|
+
[
|
175
|
+
MultiPeriodPortfolio(
|
176
|
+
name=f"{name}_{i}", portfolios=portfolios[i], **portfolio_params
|
177
|
+
)
|
178
|
+
for i in range(path_nb)
|
179
|
+
]
|
180
|
+
)
|
179
181
|
else:
|
180
182
|
# We need to re-order the test folds in case they were un-ordered by the
|
181
183
|
# CV generator.
|
@@ -29,8 +29,8 @@ class BaseOptimization(skb.BaseEstimator, ABC):
|
|
29
29
|
portfolio_params : dict, optional
|
30
30
|
Portfolio parameters passed to the portfolio evaluated by the `predict` and
|
31
31
|
`score` methods. If not provided, the `name`, `transaction_costs`,
|
32
|
-
`management_fees` and `
|
33
|
-
model and
|
32
|
+
`management_fees`, `previous_weights` and `risk_free_rate` are copied from the
|
33
|
+
optimization model and passed to the portfolio.
|
34
34
|
|
35
35
|
Attributes
|
36
36
|
----------
|
@@ -84,7 +84,12 @@ class BaseOptimization(skb.BaseEstimator, ABC):
|
|
84
84
|
ptf_kwargs = self.portfolio_params.copy()
|
85
85
|
|
86
86
|
# Set the default portfolio parameters equal to the optimization parameters
|
87
|
-
for param in [
|
87
|
+
for param in [
|
88
|
+
"transaction_costs",
|
89
|
+
"management_fees",
|
90
|
+
"previous_weights",
|
91
|
+
"risk_free_rate",
|
92
|
+
]:
|
88
93
|
if param not in ptf_kwargs and hasattr(self, param):
|
89
94
|
ptf_kwargs[param] = getattr(self, param)
|
90
95
|
|
@@ -97,15 +102,17 @@ class BaseOptimization(skb.BaseEstimator, ABC):
|
|
97
102
|
# For a 2D array we return a population of portfolios.
|
98
103
|
if self.weights_.ndim == 2:
|
99
104
|
n_portfolios = self.weights_.shape[0]
|
100
|
-
return Population(
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
return Population(
|
106
|
+
[
|
107
|
+
Portfolio(
|
108
|
+
X=X,
|
109
|
+
weights=self.weights_[i],
|
110
|
+
name=f"ptf{i} - {name}",
|
111
|
+
**ptf_kwargs,
|
112
|
+
)
|
113
|
+
for i in range(n_portfolios)
|
114
|
+
]
|
115
|
+
)
|
109
116
|
return Portfolio(X=X, weights=self.weights_, name=name, **ptf_kwargs)
|
110
117
|
|
111
118
|
def score(self, X: npt.ArrayLike, y: npt.ArrayLike = None) -> float:
|
@@ -8,6 +8,7 @@
|
|
8
8
|
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
|
9
9
|
|
10
10
|
from abc import ABC, abstractmethod
|
11
|
+
from typing import Any
|
11
12
|
|
12
13
|
import numpy as np
|
13
14
|
import numpy.typing as npt
|
@@ -183,8 +184,8 @@ class BaseHierarchicalOptimization(BaseOptimization, ABC):
|
|
183
184
|
portfolio_params : dict, optional
|
184
185
|
Portfolio parameters passed to the portfolio evaluated by the `predict` and
|
185
186
|
`score` methods. If not provided, the `name`, `transaction_costs`,
|
186
|
-
`management_fees` and `
|
187
|
-
model and
|
187
|
+
`management_fees`, `previous_weights` and `risk_free_rate` are copied from the
|
188
|
+
optimization model and passed to the portfolio.
|
188
189
|
|
189
190
|
Attributes
|
190
191
|
----------
|
@@ -235,7 +236,7 @@ class BaseHierarchicalOptimization(BaseOptimization, ABC):
|
|
235
236
|
self,
|
236
237
|
value: float | dict | np.ndarray | list,
|
237
238
|
n_assets: int,
|
238
|
-
fill_value:
|
239
|
+
fill_value: Any,
|
239
240
|
name: str,
|
240
241
|
) -> np.ndarray:
|
241
242
|
"""Convert input to cleaned 1D array
|
@@ -250,7 +251,7 @@ class BaseHierarchicalOptimization(BaseOptimization, ABC):
|
|
250
251
|
n_assets : int
|
251
252
|
Number of assets. Used to verify the shape of the converted array.
|
252
253
|
|
253
|
-
fill_value :
|
254
|
+
fill_value : Any
|
254
255
|
When `items` is a dictionary, elements that are not in `asset_names` are
|
255
256
|
filled with `fill_value` in the converted array.
|
256
257
|
|
@@ -204,8 +204,8 @@ class HierarchicalEqualRiskContribution(BaseHierarchicalOptimization):
|
|
204
204
|
portfolio_params : dict, optional
|
205
205
|
Portfolio parameters passed to the portfolio evaluated by the `predict` and
|
206
206
|
`score` methods. If not provided, the `name`, `transaction_costs`,
|
207
|
-
`management_fees` and `
|
208
|
-
model and
|
207
|
+
`management_fees`, `previous_weights` and `risk_free_rate` are copied from the
|
208
|
+
optimization model and passed to the portfolio.
|
209
209
|
|
210
210
|
Attributes
|
211
211
|
----------
|
@@ -6,7 +6,6 @@
|
|
6
6
|
# The risk measure generalization and constraint features are derived
|
7
7
|
# from Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
|
8
8
|
|
9
|
-
|
10
9
|
import numpy as np
|
11
10
|
import numpy.typing as npt
|
12
11
|
import pandas as pd
|
@@ -205,8 +204,8 @@ class HierarchicalRiskParity(BaseHierarchicalOptimization):
|
|
205
204
|
portfolio_params : dict, optional
|
206
205
|
Portfolio parameters passed to the portfolio evaluated by the `predict` and
|
207
206
|
`score` methods. If not provided, the `name`, `transaction_costs`,
|
208
|
-
`management_fees` and `
|
209
|
-
model and
|
207
|
+
`management_fees`, `previous_weights` and `risk_free_rate` are copied from the
|
208
|
+
optimization model and passed to the portfolio.
|
210
209
|
|
211
210
|
Attributes
|
212
211
|
----------
|
@@ -9,6 +9,7 @@
|
|
9
9
|
import warnings
|
10
10
|
from abc import ABC, abstractmethod
|
11
11
|
from enum import auto
|
12
|
+
from typing import Any
|
12
13
|
|
13
14
|
import cvxpy as cp
|
14
15
|
import cvxpy.constraints.constraint as cpc
|
@@ -403,8 +404,8 @@ class ConvexOptimization(BaseOptimization, ABC):
|
|
403
404
|
portfolio_params : dict, optional
|
404
405
|
Portfolio parameters passed to the portfolio evaluated by the `predict` and
|
405
406
|
`score` methods. If not provided, the `name`, `transaction_costs`,
|
406
|
-
`management_fees` and `
|
407
|
-
model and
|
407
|
+
`management_fees`, `previous_weights` and `risk_free_rate` are copied from the
|
408
|
+
optimization model and passed to the portfolio.
|
408
409
|
|
409
410
|
Attributes
|
410
411
|
----------
|
@@ -575,7 +576,7 @@ class ConvexOptimization(BaseOptimization, ABC):
|
|
575
576
|
self,
|
576
577
|
value: float | dict | npt.ArrayLike | None,
|
577
578
|
n_assets: int,
|
578
|
-
fill_value:
|
579
|
+
fill_value: Any,
|
579
580
|
name: str,
|
580
581
|
) -> float | np.ndarray:
|
581
582
|
"""Convert input to cleaned float or ndarray.
|
@@ -588,7 +589,7 @@ class ConvexOptimization(BaseOptimization, ABC):
|
|
588
589
|
n_assets : int
|
589
590
|
Number of assets. Used to verify the shape of the converted array.
|
590
591
|
|
591
|
-
fill_value :
|
592
|
+
fill_value : Any
|
592
593
|
When `items` is a dictionary, elements that are not in `asset_names` are
|
593
594
|
filled with `fill_value` in the converted array.
|
594
595
|
|