Add activation thresholds: qualified-signal defaults and views
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 32s
Deploy / deploy (push) Successful in 24s

Admin-configurable thresholds (min R:R, default 2.0; min confidence,
default 70%) defining what counts as an actionable signal:

- Admin Settings: new Activation Thresholds panel
  (GET/PUT /admin/settings/activation)
- GET /trades/activation exposes values to all users with access
- Signals/Setups: filters initialize from activation values
- Track Record: "Qualified signals only" toggle (default on) via
  min_rr/min_confidence params on /trades/performance; the
  confidence breakdown always covers the full population so the
  thresholds can be validated against outcomes
- Dashboard: "Qualified" metric and qualified-first Top Setups
- Outcome evaluator unchanged: every setup is still evaluated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 18:16:04 +02:00
parent d139dd0390
commit 6da65b8d8f
20 changed files with 440 additions and 29 deletions
+39
View File
@@ -270,3 +270,42 @@ class TestGetPerformanceStats:
assert stats["overall"]["losses"] == 1
assert stats["overall"]["hit_rate"] == 0.0
assert stats["overall"]["avg_r"] == -1.0
async def test_activation_filters_apply_to_overall_but_not_confidence(
self, db_session: AsyncSession
):
ticker = await _make_ticker(db_session)
# Qualified: high confidence, high R:R
db_session.add(_make_setup(
ticker, rr=3.0, confidence_score=80.0, actual_outcome=OUTCOME_TARGET_HIT,
))
# Unqualified: low confidence
db_session.add(_make_setup(
ticker, rr=3.0, confidence_score=40.0, actual_outcome=OUTCOME_STOP_HIT,
))
# Unqualified: low R:R
db_session.add(_make_setup(
ticker, rr=1.2, confidence_score=90.0, actual_outcome=OUTCOME_STOP_HIT,
))
await db_session.flush()
stats = await get_performance_stats(db_session, min_rr=2.0, min_confidence=70.0)
# Overall covers only the qualified setup
assert stats["overall"]["total"] == 1
assert stats["overall"]["wins"] == 1
assert stats["overall"]["hit_rate"] == 100.0
# Confidence breakdown still covers the full population
total_in_confidence = sum(
bucket["total"] for bucket in stats["by_confidence"].values()
)
assert total_in_confidence == 3
async def test_no_filters_returns_full_population(self, db_session: AsyncSession):
ticker = await _make_ticker(db_session)
db_session.add(_make_setup(ticker, rr=1.2, confidence_score=10.0, actual_outcome=OUTCOME_TARGET_HIT))
await db_session.flush()
stats = await get_performance_stats(db_session)
assert stats["overall"]["total"] == 1