Fix sidebar username, Signals filter clarity and layout
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 35s
Deploy / deploy (push) Successful in 24s

- JWT now carries a username claim; sidebar shows "Signed in as <name>"
  instead of the bare user id (sub). Re-login required for the new claim.
- Signals: Min R:R / Min Confidence inputs reflect the effective filter —
  auto-filled from the activation gate when "Qualified only" is on, reset
  to 0 when off (no more misleading 0 while the gate is active).
- Signals layout: Run Scanner moved to its own action row (it's a job
  trigger, not a filter); qualified toggle grouped with the refinement
  filters under one Filters panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 12:11:39 +02:00
parent 33f6baca6b
commit 5a0e8c8258
11 changed files with 178 additions and 125 deletions
+1
View File
@@ -16,6 +16,7 @@ class TradeTargetResponse(BaseModel):
classification: str
sr_level_id: int
sr_strength: float
is_primary: bool = False
class RecommendationSummaryResponse(BaseModel):
+1
View File
@@ -59,6 +59,7 @@ async def login(db: AsyncSession, username: str, password: str) -> str:
payload = {
"sub": str(user.id),
"username": user.username,
"role": user.role,
"exp": datetime.now(timezone.utc) + timedelta(minutes=settings.jwt_expiry_minutes),
}
+34
View File
@@ -443,6 +443,29 @@ def _build_reasoning(
)
PRIMARY_TARGET_MIN_RR = 1.5
def _select_primary_target(targets: list[dict], min_rr: float = PRIMARY_TARGET_MIN_RR) -> dict | None:
"""Primary = the most LIKELY target that still offers real asymmetry.
Among targets clearing a minimal R:R floor, pick the highest probability
(tie-break by R:R). This fixes the old pick, which ignored probability and
could land on the furthest, least-likely 'lottery' level. Stronger-reward
levels remain in the table as stretch targets. Falls back to the highest-R:R
target if nothing clears the floor.
"""
if not targets:
return None
worthwhile = [t for t in targets if float(t.get("rr_ratio", 0.0)) >= min_rr]
pool = worthwhile or targets
return max(
pool,
key=lambda t: (float(t.get("probability", 0.0)), float(t.get("rr_ratio", 0.0))),
)
async def enhance_trade_setup(
db: AsyncSession,
ticker: Ticker,
@@ -494,6 +517,17 @@ async def enhance_trade_setup(
config=config,
)
# Primary target = most-likely target with real asymmetry (see
# _select_primary_target), not the old quality-score pick that ignored
# probability. Sync the setup's headline target/rr_ratio so the chart, gate
# and outcome eval all agree with the table's starred row.
primary = _select_primary_target(targets)
if primary is not None:
for target in targets:
target["is_primary"] = target is primary
setup.target = round(float(primary["price"]), 4)
setup.rr_ratio = round(float(primary["rr_ratio"]), 4)
# Per-setup conflicts (target availability is specific to this setup)
setup_conflicts = list(conflicts)
if len(targets) < 3: