feat(factory): marginal Sharpe contribution (selection objective)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-14 19:54:08 +02:00
parent 90511dbb83
commit e48c0c2094
2 changed files with 52 additions and 0 deletions

View File

@@ -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

View File

@@ -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))