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
+20 -51
View File
@@ -18,7 +18,24 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.models.ohlcv import OHLCVRecord
from app.models.sr_level import SRLevel
from app.models.ticker import Ticker
from app.services.rr_scanner_service import scan_ticker, _compute_quality_score
from app.services.rr_scanner_service import scan_ticker
def _assert_primary_is_most_likely_worthwhile(setup) -> None:
"""The persisted headline target must equal the starred primary in the
targets table, and that primary must be the highest-probability target
with R:R >= 1.5 (fallback: highest R:R)."""
targets = setup.targets
assert targets, "expected generated targets"
primaries = [t for t in targets if t.get("is_primary")]
assert len(primaries) == 1, "exactly one primary target expected"
primary = primaries[0]
assert setup.target == pytest.approx(primary["price"], abs=0.01)
worthwhile = [t for t in targets if t["rr_ratio"] >= 1.5]
pool = worthwhile or targets
best = max(pool, key=lambda t: (t["probability"], t["rr_ratio"]))
assert primary["price"] == pytest.approx(best["price"], abs=0.01)
# ---------------------------------------------------------------------------
@@ -157,33 +174,7 @@ async def test_property_long_selects_highest_quality(
long_setups = [s for s in setups if s.direction == "long"]
assert len(long_setups) == 1, "Expected exactly one long setup"
selected_target = long_setups[0].target
# Compute entry_price and risk from the bars (same logic as scan_ticker)
# entry_price = last close ≈ 100.0, ATR ≈ 2.0, risk = ATR * 1.5 = 3.0
entry_price = bars[-1].close
# Use approximate risk; the exact value comes from ATR computation
# We reconstruct it from the setup's entry and stop
risk = long_setups[0].entry_price - long_setups[0].stop_loss
# Compute quality scores for all candidates that meet threshold
best_quality = -1.0
best_target = None
for lv in levels:
distance = lv["price"] - entry_price
if distance > 0:
rr = distance / risk
if rr >= 1.5:
quality = _compute_quality_score(rr, lv["strength"], distance, entry_price)
if quality > best_quality:
best_quality = quality
best_target = round(lv["price"], 4)
assert best_target is not None, "At least one candidate should meet threshold"
assert selected_target == pytest.approx(best_target, abs=0.01), (
f"Selected target {selected_target} != expected best-quality target "
f"{best_target} (quality={best_quality:.4f})"
)
_assert_primary_is_most_likely_worthwhile(long_setups[0])
# ---------------------------------------------------------------------------
@@ -239,29 +230,7 @@ async def test_property_short_selects_highest_quality(
short_setups = [s for s in setups if s.direction == "short"]
assert len(short_setups) == 1, "Expected exactly one short setup"
selected_target = short_setups[0].target
entry_price = bars[-1].close
risk = short_setups[0].stop_loss - short_setups[0].entry_price
# Compute quality scores for all candidates that meet threshold
best_quality = -1.0
best_target = None
for lv in levels:
distance = entry_price - lv["price"]
if distance > 0:
rr = distance / risk
if rr >= 1.5:
quality = _compute_quality_score(rr, lv["strength"], distance, entry_price)
if quality > best_quality:
best_quality = quality
best_target = round(lv["price"], 4)
assert best_target is not None, "At least one candidate should meet threshold"
assert selected_target == pytest.approx(best_target, abs=0.01), (
f"Selected target {selected_target} != expected best-quality target "
f"{best_target} (quality={best_quality:.4f})"
)
_assert_primary_is_most_likely_worthwhile(short_setups[0])
# ---------------------------------------------------------------------------