Match Horizon mockup structure; restore qualified-first radar
Fixes from first prod review of the redesign: - Top navigation bar (TopBar) replaces the sidebar, like the mockup: wordmark + sections + ticker search + jobs/status/logout; content in a centered max-width column. MobileNav unchanged. - Radar panel: qualified setups lead again (the actionable list, as the old Top Setups did); non-qualified sit behind an "N below the gate" toggle with per-rule reasons, auto-open only when nothing qualifies. Adds mini radar fingerprints for the top 3 qualified (dimension scores from the rankings endpoint, fixed axis order). - Dashboard layout: open positions and radar side by side (xl), as in the mockup. - ScoreCard: the radar fingerprint was inside the showComposite block, which the ticker page disables - it never rendered. Now it shows whenever >= 3 dimensions exist. - TradeChart: fresh positions with < 2 bars since entry rendered no chart; now pads with ~10 pre-entry context bars (gray) and marks the entry point. First open position auto-expands so the chart is visible without a click. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -237,11 +237,14 @@ export function TradeChart({
|
||||
openedAt: string;
|
||||
}) {
|
||||
const openedDate = openedAt.slice(0, 10);
|
||||
const closes = bars
|
||||
.filter((b) => b.date.slice(0, 10) >= openedDate)
|
||||
.map((b) => b.close);
|
||||
const series = [entry, ...closes];
|
||||
if (series.length < 3) return null;
|
||||
const firstIdx = bars.findIndex((b) => b.date.slice(0, 10) >= openedDate);
|
||||
// Fresh trades have few bars since entry — pad with context so the chart
|
||||
// still reads (gray before entry, colored after).
|
||||
const CONTEXT_BARS = 10;
|
||||
const start = firstIdx === -1 ? Math.max(0, bars.length - CONTEXT_BARS) : Math.max(0, firstIdx - CONTEXT_BARS);
|
||||
const series = bars.slice(start).map((b) => b.close);
|
||||
const entryIdx = firstIdx === -1 ? series.length - 1 : firstIdx - start;
|
||||
if (series.length < 2) return null;
|
||||
|
||||
const w = 560; const h = 150;
|
||||
const padL = 8; const padR = 96; const padT = 10; const padB = 12;
|
||||
@@ -259,7 +262,13 @@ export function TradeChart({
|
||||
const plotH = h - padT - padB;
|
||||
const px = (i: number) => padL + (i / (series.length - 1)) * plotW;
|
||||
const py = (v: number) => padT + plotH - ((v - lo) / (hi - lo)) * plotH;
|
||||
const path = series.map((v, i) => `${i === 0 ? 'M' : 'L'}${px(i).toFixed(1)},${py(v).toFixed(1)}`).join(' ');
|
||||
const seg = (from: number, to: number) =>
|
||||
series
|
||||
.slice(from, to + 1)
|
||||
.map((v, i) => `${i === 0 ? 'M' : 'L'}${px(from + i).toFixed(1)},${py(v).toFixed(1)}`)
|
||||
.join(' ');
|
||||
const contextPath = entryIdx > 0 ? seg(0, entryIdx) : null;
|
||||
const tradePath = seg(entryIdx, series.length - 1);
|
||||
const last = series[series.length - 1];
|
||||
const perShare = isShort ? entry - last : last - entry;
|
||||
const col = perShare >= 0 ? 'var(--up)' : 'var(--down)';
|
||||
@@ -288,7 +297,11 @@ export function TradeChart({
|
||||
target {fmt(target)} {isShort ? '↓' : '↑'}
|
||||
</text>
|
||||
)}
|
||||
<path d={path} fill="none" stroke={col} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
|
||||
{contextPath && (
|
||||
<path d={contextPath} fill="none" stroke="var(--ink-3)" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" opacity="0.7" />
|
||||
)}
|
||||
<path d={tradePath} fill="none" stroke={col} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
|
||||
<circle cx={px(entryIdx)} cy={py(entry)} r="3.5" fill="var(--ink-2)" stroke="var(--surface)" strokeWidth="1.5" />
|
||||
<circle cx={px(series.length - 1)} cy={py(last)} r="4" fill={col} stroke="var(--surface)" strokeWidth="2" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user