Files
fxhnt/tests/unit/test_allocation_engine.py

80 lines
4.2 KiB
Python

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