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>
101 lines
4.5 KiB
Python
101 lines
4.5 KiB
Python
import datetime as dt
|
|
|
|
from fxhnt.adapters.persistence.cockpit_models import (
|
|
ForwardSummaryRow,
|
|
StrategyAllocationRow,
|
|
StrategyFundingRow,
|
|
)
|
|
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
|
|
from fxhnt.application.dashboard_service import DashboardService
|
|
|
|
|
|
def _seed(tmp_path):
|
|
repo = ForwardNavRepo(f"sqlite:///{tmp_path/'c.db'}")
|
|
repo.migrate()
|
|
at = dt.datetime(2026, 7, 10)
|
|
def _sum(sid, tr):
|
|
return ForwardSummaryRow(strategy_id=sid, as_of="2026-07-10", days=5, nav=1.0 + tr, total_return=tr,
|
|
sharpe=1.0, maxdd=-0.05, gate_status="WAIT", gate_reason="", src_updated_at=at)
|
|
from sqlalchemy.orm import Session
|
|
with Session(repo._engine) as s:
|
|
s.add_all([_sum("bybit_4edge", 0.0031), _sum("bybit_4edge_exec", 0.0019)])
|
|
s.commit()
|
|
repo.replace_allocations([StrategyAllocationRow(strategy_id="bybit_4edge", target_weight=0.54,
|
|
target_dollars=18900.0, policy_version=1, policy_hash="h", as_of="2026-07-10", computed_at=at)], at)
|
|
repo.replace_funding([
|
|
StrategyFundingRow(strategy_id="bybit_4edge", state="funded", label="funded", decay_run=0,
|
|
recovery_run=0, first_pass_date=None, policy_version=2, policy_hash="h",
|
|
as_of="2026-07-10", computed_at=at),
|
|
StrategyFundingRow(strategy_id="multistrat", state="de-funded", label="de-funded", decay_run=0,
|
|
recovery_run=0, first_pass_date="2026-06-20", policy_version=2, policy_hash="h",
|
|
as_of="2026-07-10", computed_at=at),
|
|
], at)
|
|
return repo
|
|
|
|
|
|
def test_fleet_carries_allocation_funding_and_exec_pairing(tmp_path):
|
|
svc = DashboardService(_seed(tmp_path))
|
|
rows = {f.strategy_id: f for f in svc.fleet()}
|
|
b = rows["bybit_4edge"]
|
|
assert b.alloc_dollars == 18900.0 and abs(b.alloc_weight - 0.54) < 1e-9
|
|
assert b.funding_label == "funded"
|
|
# exec pairing: bybit_4edge modeled paired with bybit_4edge_exec (return 0.19% vs 0.31% -> Δ -0.12%)
|
|
assert b.exec_twin_sid == "bybit_4edge_exec"
|
|
assert abs(b.exec_return_pct - 0.19) < 1e-6
|
|
assert abs(b.exec_divergence_pct - (-0.12)) < 1e-6
|
|
# a de-funded edge
|
|
assert rows["multistrat"].funding_label == "de-funded"
|
|
# the executed twin is flagged for hiding at top level
|
|
assert "bybit_4edge_exec" in svc.exec_twin_sids()
|
|
|
|
|
|
def test_overview_capital_meter(tmp_path):
|
|
ov = DashboardService(_seed(tmp_path)).overview()
|
|
assert ov.gross_deployed == 18900.0
|
|
# ceiling reflects the RESOLVED allocation policy (default settings -> allow_live False -> paper $1M),
|
|
# NOT the old hardcoded $35k — so the meter matches the ceiling the engine actually sizes against.
|
|
assert ov.ceiling == 1_000_000.0
|
|
assert ov.n_funded == 1 and ov.n_defunded == 1
|
|
|
|
|
|
def test_graceful_when_no_alloc_funding(tmp_path):
|
|
# a repo with summaries but NO allocation/funding rows -> fields default, no crash.
|
|
repo = ForwardNavRepo(f"sqlite:///{tmp_path/'e.db'}")
|
|
repo.migrate()
|
|
rows = {f.strategy_id: f for f in DashboardService(repo).fleet()}
|
|
any_row = next(iter(rows.values()))
|
|
assert any_row.alloc_dollars is None and any_row.funding_label == ""
|
|
|
|
|
|
def _seed_multistrat_pair(tmp_path, db_name):
|
|
repo = ForwardNavRepo(f"sqlite:///{tmp_path/db_name}")
|
|
repo.migrate()
|
|
at = dt.datetime(2026, 7, 10)
|
|
def _sum(sid, tr):
|
|
return ForwardSummaryRow(strategy_id=sid, as_of="2026-07-10", days=5, nav=1.0 + tr, total_return=tr,
|
|
sharpe=1.0, maxdd=-0.05, gate_status="WAIT", gate_reason="", src_updated_at=at)
|
|
from sqlalchemy.orm import Session
|
|
with Session(repo._engine) as s:
|
|
s.add_all([_sum("multistrat", 0.0031), _sum("multistrat_exec", 0.0019)])
|
|
s.commit()
|
|
return repo, at
|
|
|
|
|
|
def test_detail_exec_venue_from_book_state(tmp_path):
|
|
repo, at = _seed_multistrat_pair(tmp_path, "f.db")
|
|
repo.set_book_state("multistrat_exec_pos", {"positions": {}, "prices": {}, "envelope": 1e6,
|
|
"venue": "ibkr-paper"}, at)
|
|
svc = DashboardService(repo)
|
|
d = svc.detail("multistrat")
|
|
assert d.exec_twin_sid == "multistrat_exec"
|
|
assert d.exec_venue == "ibkr-paper"
|
|
|
|
|
|
def test_detail_exec_venue_none_when_unrecorded(tmp_path):
|
|
repo, at = _seed_multistrat_pair(tmp_path, "g.db")
|
|
repo.set_book_state("multistrat_exec_pos", {"positions": {}, "prices": {}, "envelope": 1e6}, at)
|
|
svc = DashboardService(repo)
|
|
d = svc.detail("multistrat")
|
|
assert d.exec_twin_sid == "multistrat_exec"
|
|
assert d.exec_venue is None
|