From d84b181f9ecd0e9790f6bedda7e769a5a1ba9895 Mon Sep 17 00:00:00 2001 From: Samarth Uday Date: Sun, 2 Aug 2026 20:13:19 +0530 Subject: [PATCH] Fix HRP SciPy linkage method validation --- pypfopt/hierarchical_portfolio.py | 13 ++++++++++++- tests/test_hrp.py | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pypfopt/hierarchical_portfolio.py b/pypfopt/hierarchical_portfolio.py index 6dc76d07..188df7bf 100644 --- a/pypfopt/hierarchical_portfolio.py +++ b/pypfopt/hierarchical_portfolio.py @@ -23,6 +23,17 @@ from pypfopt.risk_models import cov_to_corr +_VALID_LINKAGE_METHODS = { + "single", + "complete", + "average", + "weighted", + "centroid", + "median", + "ward", +} + + class HRPOpt(BaseOptimizer): """ A HRPOpt object (inheriting from BaseOptimizer) constructs a hierarchical @@ -176,7 +187,7 @@ def optimize(self, linkage_method="single"): OrderedDict weights for the HRP portfolio """ - if linkage_method not in sch._LINKAGE_METHODS: + if linkage_method not in _VALID_LINKAGE_METHODS: raise ValueError("linkage_method must be one recognised by scipy") if self.returns is None: diff --git a/tests/test_hrp.py b/tests/test_hrp.py index 369a1727..dea76b6d 100644 --- a/tests/test_hrp.py +++ b/tests/test_hrp.py @@ -21,6 +21,21 @@ def test_hrp_errors(): hrp.optimize(linkage_method="blah") +@pytest.mark.parametrize( + "linkage_method", + ["single", "complete", "average", "weighted", "centroid", "median", "ward"], +) +def test_hrp_linkage_methods(linkage_method): + df = get_data() + returns = df.pct_change().dropna(how="all") + hrp = HRPOpt(returns) + + weights = hrp.optimize(linkage_method=linkage_method) + + assert isinstance(weights, dict) + np.testing.assert_almost_equal(sum(weights.values()), 1) + + def test_hrp_portfolio(): df = get_data() returns = df.pct_change().dropna(how="all")