import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { ScatterChart, Scatter, XAxis, YAxis, ZAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, ReferenceArea, } from 'recharts'; // Lazy-loaded by TickerDetailPage so recharts stays out of the main ticker chunk. export interface FieldPoint { symbol: string; composite: number; momentum: number; } interface StandingMatrixProps { symbol: string; composite: number | null; // X for the highlighted dot (authoritative, from the scores endpoint) momentum: number | null; // Y for the highlighted dot (residual 12-1 momentum percentile) field: FieldPoint[]; // every tracked ticker, for the background cloud gateMomentum: number; // Y divider = the activation gate's momentum percentile status: 'top-pick' | 'qualified' | 'none'; confidence?: number | null; // long confidence, for the verdict sidebar } // X divider: composite midpoint between "amber" (40–70) and clearly good (>70). const QUALITY_DIV = 60; type Tone = 'emerald' | 'amber' | 'sky' | 'slate'; const TONE: Record = { emerald: { text: 'text-emerald-300', dot: '#10b981' }, amber: { text: 'text-amber-300', dot: '#f59e0b' }, sky: { text: 'text-sky-300', dot: '#38bdf8' }, slate: { text: 'text-gray-300', dot: '#94a3b8' }, }; function verdict(composite: number, momentum: number, gate: number): { label: string; tone: Tone; note: string } { const q = composite >= QUALITY_DIV; const m = momentum >= gate; if (m && q) return { label: 'Strong Buy', tone: 'emerald', note: 'Solid quality and top-tier momentum — clears the gate.' }; if (m && !q) return { label: 'Momentum', tone: 'amber', note: 'Trending hard, but quality is thin — speculative.' }; if (!m && q) return { label: 'Accumulate', tone: 'sky', note: 'Good quality; momentum not yet in the top tier.' }; return { label: 'Pass', tone: 'slate', note: 'Neither quality nor momentum stands out yet.' }; } function MatrixTip({ active, payload }: { active?: boolean; payload?: { payload: FieldPoint }[] }) { if (!active || !payload?.length) return null; const p = payload[0].payload; return (
{p.symbol}
quality {Math.round(p.composite)} · momentum{' '} {Math.round(p.momentum)}
); } function StatRow({ label, value }: { label: string; value: string }) { return (
{label} {value}
); } export default function StandingMatrix({ symbol, composite, momentum, field, gateMomentum, status, confidence, }: StandingMatrixProps) { const navigate = useNavigate(); const gate = gateMomentum > 0 ? gateMomentum : 80; const sym = symbol.toUpperCase(); const here = useMemo( () => (composite != null && momentum != null ? { symbol: sym, composite, momentum } : null), [sym, composite, momentum], ); // Background cloud excludes this ticker — it's drawn separately, highlighted. const others = useMemo(() => field.filter((p) => p.symbol.toUpperCase() !== sym), [field, sym]); const v = here ? verdict(here.composite, here.momentum, gate) : null; return (
Standing — quality × momentum vs. the field
{status === 'top-pick' && ( ★ Top Pick )} {status === 'qualified' && ( ✓ Qualified )}
{/* Quadrant shading (behind everything) */} } /> p?.symbol && navigate(`/ticker/${p.symbol}`)} shape={(props: { cx?: number; cy?: number }) => ( )} /> {here && v && ( ( )} /> )}
{v && here ? ( <>
{v.label}

{v.note}

{confidence != null && }
) : (

{composite != null && momentum == null ? 'Setup is present, but residual momentum rank is missing — the activation gate treats unranked setups as not qualified. Refresh this ticker (or wait for the daily scan) so universe ranks are attached.' : 'No active setup, so this ticker isn’t ranked on the momentum axis yet. Run the scanner to place it.'}

)}
Strong Buy — quality + momentum (top-right) Momentum — trend without the quality Accumulate — quality, awaiting momentum Pass — neither stands out

Each dot is a tracked ticker; this one is highlighted. The dashed line is the activation gate ({Math.round(gate)}th-pct residual momentum) — above it qualifies for a top pick. Click any peer to open it.

); }