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:
@@ -0,0 +1,33 @@
|
||||
import type { ActivationConfig, TradeSetup } from './types';
|
||||
|
||||
const HIGH_CONVICTION_ACTIONS = new Set(['LONG_HIGH', 'SHORT_HIGH']);
|
||||
|
||||
export function bestTargetProbability(setup: TradeSetup): number {
|
||||
return setup.targets?.length ? Math.max(...setup.targets.map((t) => t.probability)) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a setup clears the activation gate. Mirrors the backend predicate in
|
||||
* app/services/qualification.py — keep the two in sync.
|
||||
*/
|
||||
export function qualifiesSetup(setup: TradeSetup, config: ActivationConfig): boolean {
|
||||
if (setup.rr_ratio < config.min_rr) return false;
|
||||
if ((setup.confidence_score ?? 0) < config.min_confidence) return false;
|
||||
if (config.require_high_conviction && !HIGH_CONVICTION_ACTIONS.has(setup.recommended_action ?? '')) {
|
||||
return false;
|
||||
}
|
||||
if (config.exclude_conflicts && (setup.risk_level ?? '') !== 'Low') return false;
|
||||
if (config.min_target_probability > 0 && bestTargetProbability(setup) < config.min_target_probability) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Short human summary of the active gate, e.g. for tooltips/labels. */
|
||||
export function activationSummary(config: ActivationConfig): string {
|
||||
const parts = [`R:R ≥ ${config.min_rr.toFixed(1)}`, `conf ≥ ${config.min_confidence.toFixed(0)}%`];
|
||||
if (config.require_high_conviction) parts.push('high-conviction');
|
||||
if (config.exclude_conflicts) parts.push('clean');
|
||||
if (config.min_target_probability > 0) parts.push(`target ≥ ${config.min_target_probability.toFixed(0)}%`);
|
||||
return parts.join(' · ');
|
||||
}
|
||||
@@ -152,10 +152,34 @@ export interface PerformanceStats {
|
||||
by_confidence: Record<string, OutcomeBucketStats>;
|
||||
}
|
||||
|
||||
// Activation thresholds: what counts as an actionable signal
|
||||
// Activation gate: what counts as an actionable signal
|
||||
export interface ActivationConfig {
|
||||
min_rr: number;
|
||||
min_confidence: number;
|
||||
min_target_probability: number;
|
||||
require_high_conviction: boolean;
|
||||
exclude_conflicts: boolean;
|
||||
}
|
||||
|
||||
// Runtime sentiment LLM configuration
|
||||
export interface SentimentProviderConfig {
|
||||
provider: string;
|
||||
model: string;
|
||||
api_key_configured: boolean;
|
||||
api_key_source: 'database' | 'environment' | 'none';
|
||||
valid_providers: string[];
|
||||
default_models: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface SentimentTestResult {
|
||||
ok: boolean;
|
||||
provider: string;
|
||||
model: string;
|
||||
ticker?: string;
|
||||
classification?: string;
|
||||
confidence?: number;
|
||||
reasoning?: string | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface TradeTarget {
|
||||
|
||||
Reference in New Issue
Block a user