Files
fxhnt/tests/integration/test_allocation_ingest.py
jgrusewski 31bc726053 fix(cockpit): overview ceiling reads resolved policy (paper/live), not hardcoded $35k; ruff import-sort
Whole-branch Important: dashboard showed the stale hardcoded ceiling while the engine sizes against
the resolved (paper $1M / live $35k) ceiling. Task-5 Minor: ruff I001 import-sort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:51:15 +02:00

82 lines
4.4 KiB
Python

import datetime as dt
from fxhnt import registry
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
from fxhnt.application.allocation_ingest import record_allocation
from fxhnt.application.allocation_policy import (
ALLOCATION_POLICY_VERSION,
allocation_policy_hash,
resolve_allocation_policy,
)
from fxhnt.application.forward_models import ForwardNavRow as NavRowDTO
from fxhnt.config import get_settings
def test_records_allocation_for_deploy_set(tmp_path, monkeypatch):
dsn = f"sqlite:///{tmp_path}/ai.db"
repo = ForwardNavRepo(dsn)
repo.migrate()
at = dt.datetime(2026, 7, 9, 23, 30)
monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d1",
{"display_name": "D1", "sleeve": "x", "gate_spec": {}, "tier": "deploy"})
monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d2",
{"display_name": "D2", "sleeve": "x", "gate_spec": {}, "tier": "deploy"})
dates = [(dt.date(2026, 6, 1) + dt.timedelta(days=i)).isoformat() for i in range(60)] # 60 valid calendar days
for sid, flip in (("d1", 1), ("d2", 0)):
for i, dd in enumerate(dates):
repo.upsert_rows([NavRowDTO(strategy_id=sid, date=dd,
ret=(0.01 if (i % 2) == flip else -0.008), nav=1.0)], at=at)
out = record_allocation(dsn, equity=20_000.0)
assert set(out) <= {"d1", "d2"} # only deploy-set considered
assert sum(abs(v) for v in out.values()) <= 35_000.0 + 1e-6 # capped by the default ceiling
assert repo.current_allocation() == out # recorded
def test_dropped_strategy_leaves_no_stale_allocation_row(tmp_path, monkeypatch):
dsn = f"sqlite:///{tmp_path}/prune.db"
repo = ForwardNavRepo(dsn)
repo.migrate()
at = dt.datetime(2026, 7, 9, 23, 30)
base = dt.date(2026, 4, 1)
monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d1",
{"display_name": "D1", "sleeve": "x", "gate_spec": {}, "tier": "deploy"})
monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d2",
{"display_name": "D2", "sleeve": "x", "gate_spec": {}, "tier": "deploy"})
for sid, flip in (("d1", 1), ("d2", 0)):
for i in range(70):
d = (base + dt.timedelta(days=i)).isoformat()
repo.upsert_rows([NavRowDTO(strategy_id=sid, date=d,
ret=(0.01 if (i % 2) == flip else -0.008), nav=1.0)], at=at)
record_allocation(dsn, equity=20_000.0) # night 1: both deploy-tier
# night 2: d2 is no longer deploy-tier (dropped from the deploy set)
monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d2",
{"display_name": "D2", "sleeve": "x", "gate_spec": {}, "tier": "research"})
record_allocation(dsn, equity=20_000.0)
assert "d2" not in repo.current_allocation() # no stale row for the dropped strategy
def test_record_allocation_uses_resolved_policy_v2(tmp_path, monkeypatch):
dsn = f"sqlite:///{tmp_path}/resolved.db"
repo = ForwardNavRepo(dsn)
repo.migrate()
at = dt.datetime(2026, 7, 9, 23, 30)
monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d1",
{"display_name": "D1", "sleeve": "x", "gate_spec": {}, "tier": "deploy"})
monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d2",
{"display_name": "D2", "sleeve": "x", "gate_spec": {}, "tier": "deploy"})
dates = [(dt.date(2026, 6, 1) + dt.timedelta(days=i)).isoformat() for i in range(60)]
for sid, flip in (("d1", 1), ("d2", 0)):
for i, dd in enumerate(dates):
repo.upsert_rows([NavRowDTO(strategy_id=sid, date=dd,
ret=(0.01 if (i % 2) == flip else -0.008), nav=1.0)], at=at)
record_allocation(dsn, equity=200_000.0)
rows = repo.all_allocations()
assert rows # allocation was recorded
settings = get_settings()
expected_policy = resolve_allocation_policy(settings)
expected_hash = allocation_policy_hash(expected_policy, ALLOCATION_POLICY_VERSION)
assert all(r.policy_version == ALLOCATION_POLICY_VERSION for r in rows) # == 2
assert all(r.policy_hash == expected_hash for r in rows)
# default settings -> allow_live False -> paper ceiling ($1M); gross stays well under it here.
assert sum(abs(r.target_dollars) for r in rows) <= expected_policy.capital_ceiling + 1e-6