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:
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useActivation } from '../hooks/useActivation';
|
||||
import { useTrades } from '../hooks/useTrades';
|
||||
import { useWatchlist } from '../hooks/useWatchlist';
|
||||
import { usePerformance } from '../hooks/usePerformance';
|
||||
@@ -51,16 +52,25 @@ function DirectionTag({ direction }: { direction: string }) {
|
||||
export default function DashboardPage() {
|
||||
const trades = useTrades();
|
||||
const watchlist = useWatchlist();
|
||||
const activation = useActivation();
|
||||
const performance = usePerformance();
|
||||
|
||||
const topSetups: TradeSetup[] = useMemo(
|
||||
() => (trades.data ?? []).slice(0, 5),
|
||||
[trades.data],
|
||||
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],
|
||||
);
|
||||
|
||||
const highConfidenceCount = useMemo(
|
||||
() => (trades.data ?? []).filter((t) => (t.confidence_score ?? 0) >= 70).length,
|
||||
[trades.data],
|
||||
// Show qualified setups first; fall back to the full list when none qualify
|
||||
const showingQualified = qualifiedSetups.length > 0;
|
||||
const topSetups: TradeSetup[] = useMemo(
|
||||
() => (showingQualified ? qualifiedSetups : trades.data ?? []).slice(0, 5),
|
||||
[showingQualified, qualifiedSetups, trades.data],
|
||||
);
|
||||
|
||||
const topWatchlist = useMemo(
|
||||
@@ -100,10 +110,10 @@ export default function DashboardPage() {
|
||||
sub="latest per ticker & direction"
|
||||
/>
|
||||
<Metric
|
||||
label="High Confidence"
|
||||
value={String(highConfidenceCount)}
|
||||
sub="confidence ≥ 70%"
|
||||
valueClass={highConfidenceCount > 0 ? 'text-blue-300' : 'text-gray-100'}
|
||||
label="Qualified"
|
||||
value={String(qualifiedSetups.length)}
|
||||
sub={`R:R ≥ ${minRR.toFixed(1)} & conf ≥ ${minConfidence.toFixed(0)}%`}
|
||||
valueClass={qualifiedSetups.length > 0 ? 'text-blue-300' : 'text-gray-100'}
|
||||
/>
|
||||
<Metric
|
||||
label="Hit Rate"
|
||||
@@ -122,7 +132,7 @@ export default function DashboardPage() {
|
||||
<div className="grid gap-8 xl:grid-cols-5">
|
||||
{/* Top setups */}
|
||||
<div className="xl:col-span-3">
|
||||
<Section title="Top Setups" hint="by confidence">
|
||||
<Section title="Top Setups" hint={showingQualified ? 'qualified, by confidence' : 'none qualified — showing all'}>
|
||||
{trades.isLoading && <SkeletonTable rows={5} cols={5} />}
|
||||
{trades.isError && <Callout variant="error">Failed to load setups</Callout>}
|
||||
{trades.data && topSetups.length === 0 && (
|
||||
|
||||
Reference in New Issue
Block a user