fix(cockpit): IBKR paper card was falsely "not connected" — it is live via UCITS

The IBKR paper account (DU9600528) IS connected and trading: the armed
fxhnt-ucits-rebalancer (0 9 * * 1-5) books the multi-strat ETF leg through
UCITS lines (EU KID/PRIIPs blocks the US-ETF leg, whose rebalancer is
correctly suspended). Prod already rendered it ($1,029,621, 5 holdings).

Two self-inflicted "not connected" artifacts fixed:
- scripts/dev_cockpit_local.py did NOT wire ibkr_account_repo, so the LOCAL
  dev cockpit always showed the IBKR card empty regardless of real data —
  a misleading local-verification artifact. Now wires every repo prod wires.
- paper.html's new orientation banner hardcoded "not connected yet" (written
  from that stale local view). Made it data-aware (keyed on ibkr_view) so it
  states the live NLV + holdings when trading, "not connected yet" only when
  genuinely empty — it can't drift from the card beneath it.

Adds a regression assertion: connected account => banner says "live now,
trading through UCITS" and never "not connected yet".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-07-16 16:11:41 +02:00
parent c5758e30bb
commit ce464f82f4
3 changed files with 14 additions and 2 deletions

View File

@@ -13,6 +13,7 @@ import os
import uvicorn
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
from fxhnt.adapters.persistence.ibkr_account_repo import IbkrAccountRepo
from fxhnt.adapters.persistence.paper_repo import PaperRepo
from fxhnt.adapters.web.app import create_app
@@ -20,10 +21,14 @@ dsn = os.environ["FXHNT_OPERATIONAL_DSN"]
port = int(os.environ.get("FXHNT_DEV_PORT", "8801"))
# READ-ONLY: no .migrate() — never write DDL to a prod DB we're only browsing.
# Wire EVERY repo prod wires (create_app_from_settings), or a page silently degrades to an empty state that
# does NOT match prod — the IBKR paper-account card in particular reads through ibkr_account_repo; without it
# the /paper card renders "not connected yet" even when the real account is live (a misleading local artifact).
app = create_app(
ForwardNavRepo(dsn),
paper_repo=PaperRepo(dsn),
bybit_paper_repo=PaperRepo(dsn),
ibkr_account_repo=IbkrAccountRepo(dsn),
)
print(f"cockpit (read-only) on http://127.0.0.1:{port}/paper/sim?book=bybit_4edge")
uvicorn.run(app, host="127.0.0.1", port=port, log_level="warning")

View File

@@ -15,8 +15,10 @@
<div class="verdict" style="margin-bottom:18px">
<b>Two paper accounts, one job: prove the edges before real money goes in.</b>
The <b>Bybit crypto book</b> is the fund's live deploy venue — it trades the 4-edge book every day at real
costs. The <b>IBKR account</b> is the US-equity leg, not connected yet. Open either card for its holdings,
trades and forward record.
costs. The <b>IBKR account</b> is the multi-strat ETF leg —
{% if ibkr_view %}live now, trading through UCITS lines from the EU desk (${{ ibkr_rollup.total_nav|money }}
across {{ ibkr_view.all_positions|length }} holding{{ 's' if ibkr_view.all_positions|length != 1 }}){% else %}not
connected yet{% endif %}. Open either card for its holdings, trades and forward record.
</div>
<div class="cards" style="grid-template-columns:repeat(auto-fit,minmax(300px,1fr))">

View File

@@ -207,6 +207,11 @@ def test_paper_landing_shows_ibkr_holdings_count_and_behind_plan() -> None:
text = _visible_text(c.get("/paper").text)
assert "2 holdings" in text
assert "0.7% behind plan" in text # exec 3.1% vs sim 3.8% -> 0.7% behind, same math as Task 4
# The orientation banner must reflect the LIVE account state — never a stale hardcoded "not connected yet"
# while the real account is trading (the exact regression this guards: the banner is data-aware, keyed on
# ibkr_view, so it can't drift from the card it sits above).
assert "live now, trading through ucits" in text
assert "not connected yet" not in text
def test_paper_landing_empty_ibkr_account_shows_clean_empty_state_no_500() -> None: