feat(factory): marginal Sharpe contribution (selection objective)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
27
src/fxhnt/domain/diversification/marginal.py
Normal file
27
src/fxhnt/domain/diversification/marginal.py
Normal 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
|
||||
25
tests/unit/test_marginal.py
Normal file
25
tests/unit/test_marginal.py
Normal 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))
|
||||
Reference in New Issue
Block a user