From ddfaf67f1eb5f0da8919041c2d055626d800d326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Fri, 27 Feb 2026 21:35:39 +0100 Subject: [PATCH 1/4] move base_optimizer --- docs/GeneralEfficientFrontier.rst | 4 ++-- docs/MeanVariance.rst | 6 +++--- docs/OtherOptimizers.rst | 4 ++-- pypfopt/base/__init__.py | 9 +++++++++ pypfopt/{base_optimizer.py => base/_base_optimizer.py} | 0 pypfopt/black_litterman.py | 6 +++--- pypfopt/cla.py | 6 +++--- pypfopt/efficient_frontier/efficient_frontier.py | 8 +++++--- pypfopt/hierarchical_portfolio.py | 9 ++++----- tests/test_base_optimizer.py | 4 ++-- tests/test_custom_objectives.py | 2 +- tests/test_imports.py | 4 +++- 12 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 pypfopt/base/__init__.py rename pypfopt/{base_optimizer.py => base/_base_optimizer.py} (100%) diff --git a/docs/GeneralEfficientFrontier.rst b/docs/GeneralEfficientFrontier.rst index 2bc5ab60..4a7d8ac3 100644 --- a/docs/GeneralEfficientFrontier.rst +++ b/docs/GeneralEfficientFrontier.rst @@ -203,7 +203,7 @@ For example, perhaps our objective is to construct a basket of assets that best particular index, in other words, to minimise the **tracking error**. This does not fit within a mean-variance optimization paradigm, but we can still implement it in PyPortfolioOpt:: - from pypfopt.base_optimizer import BaseConvexOptimizer + from pypfopt.base import BaseConvexOptimizer from pypfopt.objective_functions import ex_post_tracking_error historic_rets = ... # dataframe of historic asset returns @@ -231,7 +231,7 @@ or a ``nonconvex_objective``, which uses ``scipy.optimize`` as the backend and t different API. For more examples, check out this `cookbook recipe `_. - .. class:: pypfopt.base_optimizer.BaseConvexOptimizer + .. class:: pypfopt.base.BaseConvexOptimizer .. automethod:: convex_objective diff --git a/docs/MeanVariance.rst b/docs/MeanVariance.rst index 5b64da92..a0a1c05d 100644 --- a/docs/MeanVariance.rst +++ b/docs/MeanVariance.rst @@ -114,9 +114,9 @@ Basic Usage If you would like to use the ``portfolio_performance`` function independently of any optimizer (e.g for debugging purposes), you can use:: - from pypfopt import base_optimizer + from pypfopt.base import portfolio_performance - base_optimizer.portfolio_performance( + portfolio_performance( weights, expected_returns, cov_matrix, verbose=True, risk_free_rate=0.02 ) @@ -135,7 +135,7 @@ EfficientFrontier inherits from the BaseConvexOptimizer class. In particular, th add constraints and objectives are documented below: -.. class:: pypfopt.base_optimizer.BaseConvexOptimizer +.. class:: pypfopt.base.BaseConvexOptimizer :noindex: .. automethod:: add_constraint diff --git a/docs/OtherOptimizers.rst b/docs/OtherOptimizers.rst index 6a70067c..8b1f4704 100644 --- a/docs/OtherOptimizers.rst +++ b/docs/OtherOptimizers.rst @@ -95,11 +95,11 @@ to code up compared to custom objective functions. To implement a custom optimizer that is compatible with the rest of PyPortfolioOpt, just extend ``BaseOptimizer`` (or ``BaseConvexOptimizer`` if you want to use ``cvxpy``), -both of which can be found in ``base_optimizer.py``. This gives you access to utility +both of which can be found in ``pypfopt.base``. This gives you access to utility methods like ``clean_weights()``, as well as making sure that any output is compatible with ``portfolio_performance()`` and post-processing methods. -.. automodule:: pypfopt.base_optimizer +.. automodule:: pypfopt.base .. autoclass:: BaseOptimizer :members: diff --git a/pypfopt/base/__init__.py b/pypfopt/base/__init__.py new file mode 100644 index 00000000..45302930 --- /dev/null +++ b/pypfopt/base/__init__.py @@ -0,0 +1,9 @@ +"""Base classes.""" + +from pypfopt.base._base_optimizer import ( + BaseOptimizer, + BaseConvexOptimizer, + portfolio_performance, +) + +__all__ = ["BaseOptimizer", "BaseConvexOptimizer", "portfolio_performance"] diff --git a/pypfopt/base_optimizer.py b/pypfopt/base/_base_optimizer.py similarity index 100% rename from pypfopt/base_optimizer.py rename to pypfopt/base/_base_optimizer.py diff --git a/pypfopt/black_litterman.py b/pypfopt/black_litterman.py index 37f0fb1d..ec4bff42 100644 --- a/pypfopt/black_litterman.py +++ b/pypfopt/black_litterman.py @@ -13,7 +13,7 @@ import numpy as np import pandas as pd -from . import base_optimizer +from pypfopt.base import BaseOptimizer, portfolio_performance def market_implied_prior_returns( @@ -97,7 +97,7 @@ def market_implied_risk_aversion(market_prices, frequency=252, risk_free_rate=0. return (r - risk_free_rate) / var -class BlackLittermanModel(base_optimizer.BaseOptimizer): +class BlackLittermanModel(BaseOptimizer): """ A BlackLittermanModel object (inheriting from BaseOptimizer) contains requires a specific input format, specifying the prior, the views, the uncertainty in views, @@ -542,7 +542,7 @@ def portfolio_performance(self, verbose=False, risk_free_rate=0.0): """ if self.posterior_cov is None: self.posterior_cov = self.bl_cov() - return base_optimizer.portfolio_performance( + return portfolio_performance( self.weights, self.posterior_rets, self.posterior_cov, diff --git a/pypfopt/cla.py b/pypfopt/cla.py index bc6d59d3..bde53b19 100644 --- a/pypfopt/cla.py +++ b/pypfopt/cla.py @@ -7,10 +7,10 @@ import numpy as np import pandas as pd -from . import base_optimizer +from pypfopt.base import BaseOptimizer, portfolio_performance -class CLA(base_optimizer.BaseOptimizer): +class CLA(BaseOptimizer): """ Instance variables: @@ -506,7 +506,7 @@ def portfolio_performance(self, verbose=False, risk_free_rate=0.0): (float, float, float) expected return, volatility, Sharpe ratio. """ - return base_optimizer.portfolio_performance( + return portfolio_performance( self.weights, self.expected_returns, self.cov_matrix, diff --git a/pypfopt/efficient_frontier/efficient_frontier.py b/pypfopt/efficient_frontier/efficient_frontier.py index 33d195de..04e9d627 100644 --- a/pypfopt/efficient_frontier/efficient_frontier.py +++ b/pypfopt/efficient_frontier/efficient_frontier.py @@ -9,10 +9,12 @@ import numpy as np import pandas as pd -from .. import base_optimizer, exceptions, objective_functions +from pypfopt.base import BaseConvexOptimizer, portfolio_performance +from .. import exceptions, objective_functions -class EfficientFrontier(base_optimizer.BaseConvexOptimizer): + +class EfficientFrontier(BaseConvexOptimizer): """ An EfficientFrontier object (inheriting from BaseConvexOptimizer) contains multiple optimization methods that can be called (corresponding to different objective @@ -500,7 +502,7 @@ def portfolio_performance(self, verbose=False, risk_free_rate=0.0): ) risk_free_rate = self._risk_free_rate - return base_optimizer.portfolio_performance( + return portfolio_performance( self.weights, self.expected_returns, self.cov_matrix, diff --git a/pypfopt/hierarchical_portfolio.py b/pypfopt/hierarchical_portfolio.py index 1207cc12..f7c44986 100644 --- a/pypfopt/hierarchical_portfolio.py +++ b/pypfopt/hierarchical_portfolio.py @@ -16,13 +16,14 @@ import numpy as np import pandas as pd +from pypfopt.base import BaseOptimizer, portfolio_performance import scipy.cluster.hierarchy as sch import scipy.spatial.distance as ssd -from . import base_optimizer, risk_models +from . import risk_models -class HRPOpt(base_optimizer.BaseOptimizer): +class HRPOpt(BaseOptimizer): """ A HRPOpt object (inheriting from BaseOptimizer) constructs a hierarchical risk parity portfolio. @@ -234,6 +235,4 @@ def portfolio_performance(self, verbose=False, risk_free_rate=0.0, frequency=252 cov = self.returns.cov() * frequency mu = self.returns.mean() * frequency - return base_optimizer.portfolio_performance( - self.weights, mu, cov, verbose, risk_free_rate - ) + return portfolio_performance(self.weights, mu, cov, verbose, risk_free_rate) diff --git a/tests/test_base_optimizer.py b/tests/test_base_optimizer.py index 75aac749..2552f66e 100644 --- a/tests/test_base_optimizer.py +++ b/tests/test_base_optimizer.py @@ -8,7 +8,7 @@ import pytest from pypfopt import EfficientFrontier, exceptions, objective_functions -from pypfopt.base_optimizer import BaseOptimizer, portfolio_performance +from pypfopt.base import BaseOptimizer, portfolio_performance from tests.utilities_for_tests import get_data, setup_efficient_frontier @@ -218,7 +218,7 @@ def test_save_weights_to_file(): def test_portfolio_performance(): """ - Cover logic in base_optimizer.portfolio_performance not covered elsewhere. + Cover logic in base.portfolio_performance not covered elsewhere. """ ef = setup_efficient_frontier() ef.min_volatility() diff --git a/tests/test_custom_objectives.py b/tests/test_custom_objectives.py index 36f47505..2d886d05 100644 --- a/tests/test_custom_objectives.py +++ b/tests/test_custom_objectives.py @@ -9,7 +9,7 @@ objective_functions, risk_models, ) -from pypfopt.base_optimizer import BaseConvexOptimizer +from pypfopt.base import BaseConvexOptimizer from tests.utilities_for_tests import get_data, setup_efficient_frontier diff --git a/tests/test_imports.py b/tests/test_imports.py index 60e3d90e..39ce7079 100644 --- a/tests/test_imports.py +++ b/tests/test_imports.py @@ -1,6 +1,8 @@ +from pypfopt.base import BaseConvexOptimizer, BaseOptimizer, portfolio_performance + + def test_import_modules(): from pypfopt import ( - base_optimizer, black_litterman, cla, discrete_allocation, From c6e7ce22209555de7953581d649064b609359436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Fri, 27 Feb 2026 21:43:00 +0100 Subject: [PATCH 2/4] lint --- pypfopt/base/__init__.py | 2 +- pypfopt/hierarchical_portfolio.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pypfopt/base/__init__.py b/pypfopt/base/__init__.py index 45302930..9aa1ab38 100644 --- a/pypfopt/base/__init__.py +++ b/pypfopt/base/__init__.py @@ -1,8 +1,8 @@ """Base classes.""" from pypfopt.base._base_optimizer import ( - BaseOptimizer, BaseConvexOptimizer, + BaseOptimizer, portfolio_performance, ) diff --git a/pypfopt/hierarchical_portfolio.py b/pypfopt/hierarchical_portfolio.py index f7c44986..6dc76d07 100644 --- a/pypfopt/hierarchical_portfolio.py +++ b/pypfopt/hierarchical_portfolio.py @@ -16,11 +16,11 @@ import numpy as np import pandas as pd -from pypfopt.base import BaseOptimizer, portfolio_performance import scipy.cluster.hierarchy as sch import scipy.spatial.distance as ssd -from . import risk_models +from pypfopt.base import BaseOptimizer, portfolio_performance +from pypfopt.risk_models import cov_to_corr class HRPOpt(BaseOptimizer): @@ -181,7 +181,7 @@ def optimize(self, linkage_method="single"): if self.returns is None: cov = self.cov_matrix - corr = risk_models.cov_to_corr(self.cov_matrix).round(6) + corr = cov_to_corr(self.cov_matrix).round(6) else: corr, cov = self.returns.corr(), self.returns.cov() From 4c974823d734f1e69fb14bc2da6d2f8a500bc249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Fri, 27 Feb 2026 21:45:19 +0100 Subject: [PATCH 3/4] Create base_optimizer.py --- pypfopt/base_optimizer.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pypfopt/base_optimizer.py diff --git a/pypfopt/base_optimizer.py b/pypfopt/base_optimizer.py new file mode 100644 index 00000000..4cb1d053 --- /dev/null +++ b/pypfopt/base_optimizer.py @@ -0,0 +1,9 @@ +"""Exports for _base_optimizer.py, for downwards compatibility.""" + +from pypfopt.base._base_optimizer import ( + BaseConvexOptimizer, + BaseOptimizer, + portfolio_performance, +) + +__all__ = ["BaseOptimizer", "BaseConvexOptimizer", "portfolio_performance"] From 7683c20aed7ba21227d8f5101dadee7fbbee9ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Fri, 27 Feb 2026 22:03:18 +0100 Subject: [PATCH 4/4] Create base_optimizer.py --- pypfopt/base_optimizer.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pypfopt/base_optimizer.py diff --git a/pypfopt/base_optimizer.py b/pypfopt/base_optimizer.py new file mode 100644 index 00000000..4cb1d053 --- /dev/null +++ b/pypfopt/base_optimizer.py @@ -0,0 +1,9 @@ +"""Exports for _base_optimizer.py, for downwards compatibility.""" + +from pypfopt.base._base_optimizer import ( + BaseConvexOptimizer, + BaseOptimizer, + portfolio_performance, +) + +__all__ = ["BaseOptimizer", "BaseConvexOptimizer", "portfolio_performance"]