Add multi-factor conviction gate to activation
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 35s
Deploy / deploy (push) Successful in 26s

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:
2026-06-13 11:50:42 +02:00
parent 6da65b8d8f
commit d53ed972d1
25 changed files with 924 additions and 110 deletions
+2
View File
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { ActivationSettings } from '../components/admin/ActivationSettings';
import { SentimentProviderSettings } from '../components/admin/SentimentProviderSettings';
import { DataCleanup } from '../components/admin/DataCleanup';
import { JobControls } from '../components/admin/JobControls';
import { PipelineReadinessPanel } from '../components/admin/PipelineReadinessPanel';
@@ -30,6 +31,7 @@ export default function AdminPage() {
{activeTab === 'Settings' && (
<div className="space-y-4">
<ActivationSettings />
<SentimentProviderSettings />
<TickerUniverseBootstrap />
<RecommendationSettings />
<SettingsForm />
+6 -8
View File
@@ -9,6 +9,7 @@ import { Section } from '../components/ui/Section';
import { SkeletonCard, SkeletonTable } from '../components/ui/Skeleton';
import { formatPrice } from '../lib/format';
import { recommendationActionLabel } from '../lib/recommendation';
import { qualifiesSetup, activationSummary } from '../lib/qualification';
import type { TradeSetup } from '../lib/types';
function fmtR(value: number | null): string {
@@ -55,15 +56,12 @@ export default function DashboardPage() {
const activation = useActivation();
const performance = usePerformance();
const minRR = activation.data?.min_rr ?? 2;
const minConfidence = activation.data?.min_confidence ?? 70;
const qualifiedSetups = useMemo(
() =>
(trades.data ?? []).filter(
(t) => t.rr_ratio >= minRR && (t.confidence_score ?? 0) >= minConfidence,
),
[trades.data, minRR, minConfidence],
activation.data
? (trades.data ?? []).filter((t) => qualifiesSetup(t, activation.data!))
: [],
[trades.data, activation.data],
);
// Show qualified setups first; fall back to the full list when none qualify
@@ -112,7 +110,7 @@ export default function DashboardPage() {
<Metric
label="Qualified"
value={String(qualifiedSetups.length)}
sub={`R:R ≥ ${minRR.toFixed(1)} & conf ≥ ${minConfidence.toFixed(0)}%`}
sub={activation.data ? activationSummary(activation.data) : 'clears the activation gate'}
valueClass={qualifiedSetups.length > 0 ? 'text-blue-300' : 'text-gray-100'}
/>
<Metric