Files
fxhnt/tests/unit/test_multistrat_exec_helpers.py

121 lines
5.3 KiB
Python

from fxhnt.application.multistrat_exec_record import build_pos_state, compute_exec_return, scaled_weights
def test_scaled_weights_bridge_and_flatten():
w = {"SPY": 0.6, "IEF": 0.4}
# envelope 10k in a 100k account → scale 0.1 → weights sum to envelope/NLV
s = scaled_weights(w, envelope=10_000.0, nlv=100_000.0)
assert abs(s["SPY"] - 0.06) < 1e-12 and abs(s["IEF"] - 0.04) < 1e-12
assert scaled_weights(w, 0.0, 100_000.0) == {"SPY": 0.0, "IEF": 0.0} # $0 envelope → flatten
assert scaled_weights(w, 10_000.0, 0.0) == {"SPY": 0.0, "IEF": 0.0} # no NLV → flatten (safe)
def test_compute_exec_return_first_run_is_none():
assert compute_exec_return({}, {"SPY": 500.0}) is None # no prior book → inception
def test_exec_return_is_envelope_basis_not_full_nlv():
# prior book: $10k envelope fully deployed in 20 SPY @ 500 (=$10k), in a (hypothetical) large account.
prior = build_pos_state({"SPY": 20.0}, {"SPY": 500.0}, envelope=10_000.0)
# SPY +10% today → book +$1000 on the $10k envelope → 10% return (NOT diluted by any idle account cash)
assert abs(compute_exec_return(prior, {"SPY": 550.0}) - 0.10) < 1e-9
def test_reallocation_capital_flow_is_not_return():
# flat prices; the envelope will grow at the NEXT rebalance (a flow) — the RETURN of the prior book is ~0
prior = build_pos_state({"SPY": 20.0}, {"SPY": 500.0}, envelope=10_000.0)
assert abs(compute_exec_return(prior, {"SPY": 500.0})) < 1e-12 # flat prices → 0%, not a jump
def test_partial_deployment_reserve_earns_zero():
# $10k envelope, only $6k deployed (12 SPY @ 500), $4k reserve. SPY +10% → +$600 on $10k = 6%.
prior = build_pos_state({"SPY": 12.0}, {"SPY": 500.0}, envelope=10_000.0)
assert abs(compute_exec_return(prior, {"SPY": 550.0}) - 0.06) < 1e-9
def test_none_prior_returns_none():
# None prior → inception, no AttributeError on .get() call
assert compute_exec_return(None, {"SPY": 500.0}) is None
def test_missing_price_degrades_not_raises():
# Position absent from today's prices uses prior price; no KeyError raised
prior = build_pos_state({"SPY": 20.0}, {"SPY": 500.0}, envelope=10_000.0)
assert compute_exec_return(prior, {}) == 0.0
def test_build_pos_state_persists_venue():
from fxhnt.application.multistrat_exec_record import build_pos_state
st = build_pos_state({"SPY": 10.0}, {"SPY": 5.0}, 1000.0, venue="ibkr-equity")
assert st["venue"] == "ibkr-equity" and st["envelope"] == 1000.0
def test_build_pos_state_venue_defaults_empty():
from fxhnt.application.multistrat_exec_record import build_pos_state
assert build_pos_state({}, {}, 0.0)["venue"] == ""
class _FakeExecSvc:
def __init__(self):
self.calls = []
def rebalance_weights(self, sid, weights, prices, *, max_gross, execute):
self.calls.append((sid, dict(weights), execute))
class _Plan:
nlv = 100000.0; orders = []; notes = []; blocked = False; executed = execute
return _Plan()
class _FakeRepo:
def __init__(self, alloc):
self._alloc = alloc; self.book = {}
def current_allocation(self): # B2a — must NOT be called in paper mode
raise AssertionError("current_allocation must not be read in paper mode")
def get_book_state(self, k):
return self.book.get(k, {})
def set_book_state(self, k, extra, at):
self.book[k] = extra
class _B2aRepo(_FakeRepo):
def current_allocation(self):
return self._alloc
def test_plan_and_record_paper_mode_bypasses_b2a():
import datetime as dt
from fxhnt.application.multistrat_exec_record import plan_and_record
repo = _FakeRepo(alloc={"multistrat": 0.0}); svc = _FakeExecSvc()
_plan, envelope = plan_and_record(
repo=repo, exec_svc=svc, within_weights={"SPY": 1.0}, prices={"SPY": 10.0},
nlv=500000.0, today="2026-07-11", at=dt.datetime(2026, 7, 11), max_gross=2.0,
execute=False, paper_envelope=1_000_000.0, account_is_paper=True, venue="ibkr-equity")
assert envelope == 500000.0 # min(1e6 paper, 5e5 nlv); B2a NOT consulted
def test_plan_and_record_paper_mode_refuses_live_account():
import datetime as dt
import pytest
from fxhnt.application.multistrat_exec_record import plan_and_record
repo = _FakeRepo(alloc={"multistrat": 0.0}); svc = _FakeExecSvc()
with pytest.raises(ValueError, match="paper-envelope refused"):
plan_and_record(
repo=repo, exec_svc=svc, within_weights={"SPY": 1.0}, prices={"SPY": 10.0},
nlv=500000.0, today="2026-07-11", at=dt.datetime(2026, 7, 11), max_gross=2.0,
execute=True, paper_envelope=1_000_000.0, account_is_paper=False, venue="ibkr-equity")
assert svc.calls == [] # NO orders on refusal
def test_plan_and_record_default_mode_reads_b2a():
import datetime as dt
from fxhnt.application.multistrat_exec_record import plan_and_record
repo = _B2aRepo(alloc={"multistrat": 25000.0}); svc = _FakeExecSvc()
_plan, envelope = plan_and_record(
repo=repo, exec_svc=svc, within_weights={"SPY": 1.0}, prices={"SPY": 10.0},
nlv=500000.0, today="2026-07-11", at=dt.datetime(2026, 7, 11), max_gross=2.0,
execute=False, paper_envelope=0.0, account_is_paper=True)
assert envelope == 25000.0 # min($25k B2a, $500k nlv)