Add activation thresholds: qualified-signal defaults and views
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user