from fxhnt.application.allocation_engine import inverse_vol_weights def test_inverse_vol_weights_favour_low_vol_and_sum_to_one(): rbs = {"lo": {"1": 0.001, "2": -0.001, "3": 0.001}, "hi": {"1": 0.10, "2": -0.10, "3": 0.10}} w = inverse_vol_weights(rbs) assert abs(sum(w.values()) - 1.0) < 1e-9 and w["lo"] > w["hi"] def test_inverse_vol_weights_all_degenerate_falls_back_to_equal(): rbs = {"a": {"1": 0.0, "2": 0.0}, "b": {"1": 0.0, "2": 0.0}} w = inverse_vol_weights(rbs) assert abs(w["a"] - 0.5) < 1e-9 and abs(w["b"] - 0.5) < 1e-9 def test_compute_allocation_uses_inverse_vol_base_capped(): from fxhnt.application.allocation_engine import compute_allocation from fxhnt.application.allocation_policy import AllocationPolicy n = 400 rbs = {"lo": {str(i): (0.002 if i % 2 else -0.002) for i in range(n)}, "hi": {str(i): (0.05 if i % 2 else -0.05) for i in range(n)}} pol = AllocationPolicy(marginal_gate=False, min_history_days=60, max_strategy_weight=0.9) w = compute_allocation(rbs, pol) assert w["lo"] > w["hi"] # low-vol gets MORE (was equal under the old code) def test_target_capital_caps_at_per_strategy_capacity(): from fxhnt.application.allocation_engine import target_capital from fxhnt.application.allocation_policy import AllocationPolicy pol = AllocationPolicy(capital_ceiling=1_000_000.0) d = target_capital({"cap_limited": 0.5, "robust": 0.5}, equity=1_000_000.0, policy=pol, capacity_ceiling={"cap_limited": 100_000.0}) assert d["cap_limited"] <= 100_000.0 and d["robust"] > d["cap_limited"] def test_target_capital_none_ceiling_unchanged(): from fxhnt.application.allocation_engine import target_capital from fxhnt.application.allocation_policy import AllocationPolicy pol = AllocationPolicy(capital_ceiling=1_000_000.0) a = target_capital({"x": 0.5}, 1_000_000.0, pol) b = target_capital({"x": 0.5}, 1_000_000.0, pol, capacity_ceiling=None) assert a == b def test_target_capital_caps_short_magnitude_sign_preserved(): from fxhnt.application.allocation_engine import target_capital from fxhnt.application.allocation_policy import AllocationPolicy pol = AllocationPolicy(capital_ceiling=1_000_000.0) d = target_capital({"s": -0.5}, equity=1_000_000.0, policy=pol, capacity_ceiling={"s": 100_000.0}) assert d["s"] == -100_000.0 # short: -min(|d|, ceiling), sign preserved def test_capacity_ceiling_is_last_aum_above_min_sharpe(): from fxhnt.application.allocation_engine import capacity_ceiling_from_curve curve = {35_000: 3.5, 100_000: 2.5, 350_000: 0.3, 1_000_000: -2.5} # xsfunding-shaped assert capacity_ceiling_from_curve(curve, 1.0) == 100_000 # last AUM with Sh >= 1.0 robust = {35_000: 2.9, 1_000_000: 2.6, 10_000_000: 1.6} assert capacity_ceiling_from_curve(robust, 1.0) == 10_000_000 # never drops -> top of range # unviable at EVERY tested AUM -> ZERO capacity (0.0), NOT None: unviable-everywhere is the OPPOSITE of # unbounded, so target_capital caps its dollars to $0. Only an EMPTY curve is None (no info -> unbounded). assert capacity_ceiling_from_curve({35_000: 0.2}, 1.0) == 0.0 # unviable even at smallest -> $0 assert capacity_ceiling_from_curve({35_000: 0.2, 100_000: -1.0}, 1.0) == 0.0 assert capacity_ceiling_from_curve({}, 1.0) is None # empty -> unbounded sentinel def test_target_capital_zero_ceiling_caps_to_zero(): from fxhnt.application.allocation_engine import capacity_ceiling_from_curve, target_capital from fxhnt.application.allocation_policy import AllocationPolicy pol = AllocationPolicy(capital_ceiling=1_000_000.0) # a strategy unviable at every AUM -> 0.0 ceiling -> its dollars are capped to $0 (min(|d|, 0) == 0), # while a None-ceiling strategy is uncapped. Proves the sentinel fix end-to-end through target_capital. zero = capacity_ceiling_from_curve({35_000: 0.2}, 1.0) assert zero == 0.0 d = target_capital({"dead": 0.5, "live": 0.5}, equity=1_000_000.0, policy=pol, capacity_ceiling={"dead": zero}) assert d["dead"] == 0.0 and d["live"] > 0.0