Files
fxhnt/tests/unit/test_allocation_engine_helpers.py
2026-07-11 20:22:54 +02:00

75 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import numpy as np
from fxhnt.application.allocation_engine import (
cvar_daily,
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
def test_cvar_daily_worst_tail_positive_loss():
# 10 days; alpha=0.10 -> worst 1 day = -0.05 -> CVaR = +0.05 (positive loss)
r = {f"d{i}": v for i, v in enumerate([0.01, 0.02, -0.05, 0.0, 0.01, 0.02, -0.01, 0.0, 0.01, 0.02])}
assert abs(cvar_daily(r, 0.10) - 0.05) < 1e-9
def test_cvar_daily_all_gains_is_zero():
r = {f"d{i}": 0.01 for i in range(10)} # worst 10% still +0.01 -> loss magnitude floored at 0
assert cvar_daily(r, 0.10) == 0.0
def test_cvar_daily_too_short_is_zero():
assert cvar_daily({"d0": -0.5}, 0.10) == 0.0
def test_cvar_daily_full_history_not_trailing():
base = {f"d{i}": 0.001 for i in range(20)}
worse = dict(base)
worse["old"] = -0.20 # an OLD bad day must change full-history CVaR
assert cvar_daily(worse, 0.10) > cvar_daily(base, 0.10)