import { useMutation, useQueryClient } from '@tanstack/react-query'; import { usePerformance } from '../hooks/usePerformance'; import { triggerJob } from '../api/admin'; import { Button } from '../components/ui/Button'; import { Callout } from '../components/ui/Callout'; import { Disclosure } from '../components/ui/Disclosure'; import { PageHeader } from '../components/ui/PageHeader'; import { Section } from '../components/ui/Section'; import { SkeletonCard } from '../components/ui/Skeleton'; import { useToast } from '../components/ui/Toast'; import { RECOMMENDATION_ACTION_LABELS } from '../lib/recommendation'; 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}
}| {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)} |
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.