diff --git a/frontend/src/components/charts/horizon.tsx b/frontend/src/components/charts/horizon.tsx index 214a03b..3fbcf72 100644 --- a/frontend/src/components/charts/horizon.tsx +++ b/frontend/src/components/charts/horizon.tsx @@ -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 ? '↓' : '↑'} )} - + {contextPath && ( + + )} + + ); diff --git a/frontend/src/components/dashboard/OpenTradesPanel.tsx b/frontend/src/components/dashboard/OpenTradesPanel.tsx index 3984953..a70ccb8 100644 --- a/frontend/src/components/dashboard/OpenTradesPanel.tsx +++ b/frontend/src/components/dashboard/OpenTradesPanel.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import type { KeyboardEvent, ReactNode } from 'react'; import { Link } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; @@ -135,6 +135,16 @@ export function OpenTradesPanel() { const close = useClosePaperTrade(); const [expandedId, setExpandedId] = useState(null); + // Open the first row once on load so the trade chart is visible without a + // click; after that the user's expand/collapse choice wins. + const autoExpanded = useRef(false); + useEffect(() => { + if (!autoExpanded.current && trades && trades.length > 0) { + autoExpanded.current = true; + setExpandedId(trades[0].id); + } + }, [trades]); + const exitLabel = policy ? policy.mode === 'atr_trailing' ? `${(policy.atr_multiplier ?? 3).toFixed(1)}x ATR trailing stop / ${policy.hold_days}d max` diff --git a/frontend/src/components/layout/AppShell.tsx b/frontend/src/components/layout/AppShell.tsx index 1516d6b..feb0844 100644 --- a/frontend/src/components/layout/AppShell.tsx +++ b/frontend/src/components/layout/AppShell.tsx @@ -1,21 +1,19 @@ import { Outlet } from 'react-router-dom'; -import Sidebar from './Sidebar'; +import TopBar from './TopBar'; import MobileNav from './MobileNav'; export default function AppShell() { return ( -
+