sorted(kept) makes the leave-one-out bench choice stable across process runs (a set's iteration order is PYTHONHASHSEED-dependent) — a reproducibility fix for a capital-allocation function. Docstring no longer claims compute_allocation/target_capital, which land in later tasks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
2.1 KiB
Python
52 lines
2.1 KiB
Python
import numpy as np
|
||
|
||
from fxhnt.application.allocation_engine import (
|
||
equal_weight_marginal_prune,
|
||
full_history_vol,
|
||
killswitch_active,
|
||
)
|
||
|
||
|
||
def test_full_history_vol_is_annualised_std():
|
||
r = {
|
||
f"2026-06-{d:02d}": v
|
||
for d, v in zip(range(1, 6), [0.01, -0.01, 0.02, -0.02, 0.0], strict=True)
|
||
}
|
||
assert (
|
||
abs(
|
||
full_history_vol(r)
|
||
- float(np.std([0.01, -0.01, 0.02, -0.02, 0.0]) * np.sqrt(365.0))
|
||
)
|
||
< 1e-12
|
||
)
|
||
assert full_history_vol({"2026-06-01": 0.01}) == 0.0 # < 2 obs → 0
|
||
|
||
|
||
def test_marginal_prune_benches_non_additive_strategy():
|
||
dates = [f"2026-06-{d:02d}" for d in range(1, 41)]
|
||
good = {d: (0.01 if i % 2 else -0.008) for i, d in enumerate(dates)} # positive-Sharpe
|
||
noise = {d: (0.03 if i % 3 == 0 else -0.02) for i, d in enumerate(dates)} # drags the book
|
||
kept = equal_weight_marginal_prune({"good": good, "noise": noise})
|
||
assert "good" in kept and "noise" not in kept
|
||
|
||
|
||
def test_killswitch_latches_on_deep_dd_and_reenters():
|
||
# a −30% run then recovery; kill at 0.25, reenter at 0.10
|
||
down = {f"2026-06-{d:02d}": -0.04 for d in range(1, 11)} # ~ -33% cumulative → breach 0.25
|
||
assert killswitch_active(down, kill_dd=0.25, reenter_dd=0.10) is True
|
||
calm = {f"2026-06-{d:02d}": 0.001 for d in range(1, 11)}
|
||
assert killswitch_active(calm, kill_dd=0.25, reenter_dd=0.10) is False
|
||
|
||
|
||
def test_marginal_prune_tie_break_is_deterministic():
|
||
# two identical NON-additive noise strategies + one good one: whichever gets benched must be STABLE
|
||
# (sorted order), not hash-order-dependent. Run it and assert the survivor set is the same each call.
|
||
dates = [f"2026-06-{d:02d}" for d in range(1, 41)]
|
||
good = {d: (0.01 if i % 2 else -0.008) for i, d in enumerate(dates)}
|
||
noise = {d: (0.03 if i % 3 == 0 else -0.02) for i, d in enumerate(dates)}
|
||
inp = {"good": good, "noiseA": dict(noise), "noiseB": dict(noise)}
|
||
r1 = equal_weight_marginal_prune(inp)
|
||
r2 = equal_weight_marginal_prune(inp)
|
||
assert r1 == r2 # deterministic
|
||
assert "good" in r1 # the good one survives
|