feat: Standing matrix on the ticker page (quality x momentum verdict)
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 42s
Deploy / deploy (push) Successful in 26s

Replace the scattered score readouts with one hero: a quality (composite) x
momentum-percentile scatter that plots this ticker against the whole field and
reads out a verdict by quadrant — Strong Buy / Momentum / Accumulate / Pass. The
dashed divider is the activation gate's momentum percentile, so "above the line =
qualifies" is visible at a glance; peers are clickable. Reuses the regime-quadrant
visual language and is lazy-loaded so recharts stays out of the main ticker chunk.

- New StandingMatrix component (composite x momentum, field cloud, verdict).
- ScoreCard gains showComposite (default true); the ticker page now renders it
  without the composite ring (composite lives in the matrix) under "Dimensions".
- Confidence + target probability stay in the recommendation panel (the trade).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 17:00:47 +02:00
parent 146dadf06f
commit 4a96f85cd9
3 changed files with 290 additions and 23 deletions
+23 -18
View File
@@ -6,6 +6,9 @@ interface ScoreCardProps {
compositeScore: number | null;
dimensions: DimensionScoreDetail[];
compositeBreakdown?: CompositeBreakdown;
/** Hide the composite ring/header when the composite is shown elsewhere
* (e.g. the Standing matrix) and this card only carries the dimension detail. */
showComposite?: boolean;
}
function scoreColor(score: number): string {
@@ -51,7 +54,7 @@ function ScoreRing({ score }: { score: number }) {
);
}
export function ScoreCard({ compositeScore, dimensions, compositeBreakdown }: ScoreCardProps) {
export function ScoreCard({ compositeScore, dimensions, compositeBreakdown, showComposite = true }: ScoreCardProps) {
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
const toggleExpand = (dimension: string) => {
@@ -60,27 +63,29 @@ export function ScoreCard({ compositeScore, dimensions, compositeBreakdown }: Sc
return (
<div className="glass p-5">
<div className="flex items-center gap-4">
{compositeScore !== null ? (
<ScoreRing score={compositeScore} />
) : (
<div className="flex h-[88px] w-[88px] items-center justify-center text-sm text-gray-500">N/A</div>
)}
<div>
<p className="text-xs text-gray-500 uppercase tracking-wider">Composite Score</p>
<p className={`text-2xl font-bold ${compositeScore !== null ? scoreColor(compositeScore) : 'text-gray-500'}`}>
{compositeScore !== null ? Math.round(compositeScore) : '—'}
</p>
{compositeBreakdown && (
<p className="mt-1 text-[10px] text-gray-500 leading-snug max-w-[200px]" data-testid="renorm-explanation">
Weighted average of available dimensions with re-normalized weights.
</p>
{showComposite && (
<div className="flex items-center gap-4">
{compositeScore !== null ? (
<ScoreRing score={compositeScore} />
) : (
<div className="flex h-[88px] w-[88px] items-center justify-center text-sm text-gray-500">N/A</div>
)}
<div>
<p className="text-xs text-gray-500 uppercase tracking-wider">Composite Score</p>
<p className={`text-2xl font-bold ${compositeScore !== null ? scoreColor(compositeScore) : 'text-gray-500'}`}>
{compositeScore !== null ? Math.round(compositeScore) : '—'}
</p>
{compositeBreakdown && (
<p className="mt-1 text-[10px] text-gray-500 leading-snug max-w-[200px]" data-testid="renorm-explanation">
Weighted average of available dimensions with re-normalized weights.
</p>
)}
</div>
</div>
</div>
)}
{dimensions.length > 0 && (
<div className="mt-5 space-y-1">
<div className={`${showComposite ? 'mt-5' : ''} space-y-1`}>
<p className="text-[10px] font-medium uppercase tracking-widest text-gray-500">Dimensions</p>
{dimensions.map((d) => {
const isExpanded = expanded[d.dimension] ?? false;