Add multi-factor conviction gate to activation
Make "qualified" mean an edge candidate, not just R:R + confidence. The gate now also requires (all admin-configurable, defaults on): - high conviction: recommended_action LONG_HIGH / SHORT_HIGH only - clean read: risk_level Low (no contradicting signals) - probable primary target: best target probability >= min (default 60) - Shared predicate: app/services/qualification.py + frontend/src/lib/qualification.ts (mirrored) - Activation config extended (min_target_probability, require_high_conviction, exclude_conflicts) with bool-aware get/update + validation - /trades/performance switched to ?qualified_only=true, applying the full gate server-side; confidence breakdown stays unfiltered - Dashboard "Qualified", Signals "Qualified only" toggle, and Track Record all use the one gate; Admin gains the new controls Sentiment provider runtime config (prior change) included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.ohlcv import OHLCVRecord
|
||||
from app.models.trade_setup import TradeSetup
|
||||
from app.services.qualification import setup_qualifies
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -180,8 +181,7 @@ def _confidence_bucket(score: float | None) -> str | None:
|
||||
|
||||
async def get_performance_stats(
|
||||
db: AsyncSession,
|
||||
min_rr: float | None = None,
|
||||
min_confidence: float | None = None,
|
||||
config: dict | None = None,
|
||||
) -> dict:
|
||||
"""Aggregate outcome statistics over all evaluated trade setups.
|
||||
|
||||
@@ -189,9 +189,10 @@ async def get_performance_stats(
|
||||
loss = -1R, expired = 0R). A positive avg_r means the signals have
|
||||
been profitable on a risk-adjusted basis.
|
||||
|
||||
min_rr / min_confidence filter the overall, direction and action
|
||||
breakdowns. The confidence breakdown deliberately stays unfiltered:
|
||||
it is the instrument for validating the thresholds themselves.
|
||||
When ``config`` (an activation-gate dict) is supplied, the overall,
|
||||
direction and action breakdowns cover only qualified setups. The
|
||||
confidence breakdown deliberately stays unfiltered: it is the
|
||||
instrument for validating the gate itself.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(TradeSetup).where(TradeSetup.actual_outcome.is_not(None))
|
||||
@@ -203,14 +204,10 @@ async def get_performance_stats(
|
||||
)
|
||||
pending_count = len(pending_result.scalars().all())
|
||||
|
||||
def qualifies(setup: TradeSetup) -> bool:
|
||||
if min_rr is not None and setup.rr_ratio < min_rr:
|
||||
return False
|
||||
if min_confidence is not None and (setup.confidence_score or 0.0) < min_confidence:
|
||||
return False
|
||||
return True
|
||||
|
||||
qualified = [s for s in evaluated if qualifies(s)]
|
||||
if config is not None:
|
||||
qualified = [s for s in evaluated if setup_qualifies(s, config)]
|
||||
else:
|
||||
qualified = evaluated
|
||||
|
||||
by_direction: dict[str, list[TradeSetup]] = {}
|
||||
by_action: dict[str, list[TradeSetup]] = {}
|
||||
|
||||
Reference in New Issue
Block a user