From e48c0c209492606ea348fd742e37cc2d097db2be Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Sun, 14 Jun 2026 19:54:08 +0200 Subject: [PATCH] feat(factory): marginal Sharpe contribution (selection objective) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/fxhnt/domain/diversification/marginal.py | 27 ++++++++++++++++++++ tests/unit/test_marginal.py | 25 ++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/fxhnt/domain/diversification/marginal.py create mode 100644 tests/unit/test_marginal.py diff --git a/src/fxhnt/domain/diversification/marginal.py b/src/fxhnt/domain/diversification/marginal.py new file mode 100644 index 0000000..a16145e --- /dev/null +++ b/src/fxhnt/domain/diversification/marginal.py @@ -0,0 +1,27 @@ +"""Marginal contribution of a candidate sleeve to the book: the increase in annualized Sharpe from blending +it in at `weight`. Positive = the sleeve improves the risk-adjusted book (the selection objective). NaN if +the series are too short to estimate.""" +from __future__ import annotations + +import numpy as np + +_ANN = np.sqrt(252.0) + + +def _sharpe(x: np.ndarray) -> float: + x = x[np.isfinite(x)] + if len(x) < 2 or x.std() == 0: + return float("nan") + return float(x.mean() / x.std() * _ANN) + + +def marginal_sharpe(book: np.ndarray, sleeve: np.ndarray, *, weight: float = 0.2) -> float: + n = min(len(book), len(sleeve)) + if n < 2: + return float("nan") + b, s = book[-n:], sleeve[-n:] + base = _sharpe(b) + blended = _sharpe((1.0 - weight) * b + weight * s) + if not (np.isfinite(base) and np.isfinite(blended)): + return float("nan") + return blended - base diff --git a/tests/unit/test_marginal.py b/tests/unit/test_marginal.py new file mode 100644 index 0000000..ac58c1c --- /dev/null +++ b/tests/unit/test_marginal.py @@ -0,0 +1,25 @@ +"""Marginal contribution = the change in book Sharpe from adding a sleeve at a small weight.""" +from __future__ import annotations + +import numpy as np + +from fxhnt.domain.diversification.marginal import marginal_sharpe + + +def test_uncorrelated_positive_sleeve_adds_sharpe() -> None: + rng = np.random.default_rng(0) + book = rng.normal(0.0005, 0.01, 1000) + sleeve = rng.normal(0.0005, 0.01, 1000) # independent, positive drift + assert marginal_sharpe(book, sleeve, weight=0.2) > 0.0 + + +def test_duplicate_book_adds_little() -> None: + rng = np.random.default_rng(1) + book = rng.normal(0.0005, 0.01, 1000) + add = marginal_sharpe(book, book.copy(), weight=0.2) # same stream -> no diversification + indep = marginal_sharpe(book, rng.normal(0.0005, 0.01, 1000), weight=0.2) + assert indep > add + + +def test_too_short_returns_nan() -> None: + assert np.isnan(marginal_sharpe(np.array([0.01]), np.array([0.01]), weight=0.2))