import { useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useActivation } from '../../hooks/useActivation'; import { activationSummary } from '../../lib/qualification'; import { usePerformance } from '../../hooks/usePerformance'; import { triggerJob, resetTrackRecord } from '../../api/admin'; import { Button } from '../ui/Button'; import { Callout } from '../ui/Callout'; import { Disclosure } from '../ui/Disclosure'; import { Section } from '../ui/Section'; import { SkeletonCard } from '../ui/Skeleton'; import { useToast } from '../ui/Toast'; import { RECOMMENDATION_ACTION_LABELS } from '../../lib/recommendation'; import { BacktestPanel } from './BacktestPanel'; import type { OutcomeBucketStats } from '../../lib/types'; function fmtR(value: number | null): string { if (value === null) return '—'; return `${value > 0 ? '+' : ''}${value.toFixed(2)}R`; } function fmtPct(value: number | null): string { return value === null ? '—' : `${value.toFixed(1)}%`; } function rColor(value: number | null): string { if (value === null) return 'text-gray-400'; if (value > 0) return 'text-emerald-400'; if (value < 0) return 'text-red-400'; return 'text-gray-300'; } function StatCard({ label, value, valueClass = 'text-gray-100', sub }: { label: string; value: string; valueClass?: string; sub?: string; }) { return (

{label}

{value}

{sub &&

{sub}

}
); } function actionLabel(key: string): string { return RECOMMENDATION_ACTION_LABELS[key as keyof typeof RECOMMENDATION_ACTION_LABELS] ?? key; } function BreakdownTable({ rows, labelHeader, mapLabel }: { rows: Record; labelHeader: string; mapLabel?: (key: string) => string; }) { const entries = Object.entries(rows); if (entries.length === 0) { return No evaluated setups in this breakdown yet.; } return (
{entries.map(([key, stats]) => ( ))}
{labelHeader} Setups Wins Losses Expired Hit Rate Avg R Total R
{mapLabel ? mapLabel(key) : key} {stats.total} {stats.wins} {stats.losses} {stats.expired} {fmtPct(stats.hit_rate)} {fmtR(stats.avg_r)} {fmtR(stats.total_r)}
); } export function TrackRecordPanel() { const [qualifiedOnly, setQualifiedOnly] = useState(true); const activation = useActivation(); const { data, isLoading, isError, error } = usePerformance( qualifiedOnly ? { qualified_only: true } : undefined, ); const queryClient = useQueryClient(); const toast = useToast(); const evaluateMutation = useMutation({ mutationFn: () => triggerJob('outcome_evaluator'), onSuccess: () => { toast.addToast('success', 'Outcome evaluation triggered. Stats will refresh shortly.'); setTimeout(() => queryClient.invalidateQueries({ queryKey: ['performance'] }), 3000); }, onError: () => { toast.addToast('error', 'Failed to trigger outcome evaluation'); }, }); const resetMutation = useMutation({ mutationFn: () => resetTrackRecord(), onSuccess: (data) => { toast.addToast('success', `Track record reset — ${data.trade_setups} setups cleared. Run the scanner to rebuild.`); queryClient.invalidateQueries({ queryKey: ['performance'] }); queryClient.invalidateQueries({ queryKey: ['trades'] }); }, onError: () => { toast.addToast('error', 'Failed to reset track record'); }, }); const onReset = () => { if ( window.confirm( 'Reset the track record? This permanently deletes ALL trade setups and their outcomes. ' + 'Live setups will regenerate on the next R:R scan. This cannot be undone.', ) ) { resetMutation.mutate(); } }; return (

Confidence breakdown always covers all setups.

Each setup is replayed against the daily bars after its detection: a{' '} win means the target was reached before the stop, a loss means the stop was hit first (bars where both levels fall inside the same day count conservatively as losses). Setups with neither level hit within 30 trading days expire at 0R. Avg R is the expectancy per trade: wins earn their R:R ratio, losses cost −1R — a positive value means the signals have been profitable on a risk-adjusted basis. The evaluator runs nightly after OHLCV collection.

{isLoading && (
)} {isError && ( {error instanceof Error ? error.message : 'Failed to load performance stats'} )} {data && data.overall.total === 0 && ( {qualifiedOnly ? 'No evaluated setups meet the activation thresholds yet. Untick "Qualified signals only" to see all evaluated setups, or wait for more outcomes.' : 'No evaluated setups yet. Outcomes appear once setups are old enough for their stop or target to be hit — the evaluator runs nightly, or click Evaluate Now.'} {data.pending > 0 && ` ${data.pending} setup${data.pending === 1 ? '' : 's'} pending evaluation.`} )} {data && data.overall.total > 0 && ( <>
)}
); }