diff --git a/web/app/[code]/page.tsx b/web/app/[code]/page.tsx index df7eb68..e1bfcad 100644 --- a/web/app/[code]/page.tsx +++ b/web/app/[code]/page.tsx @@ -161,22 +161,62 @@ export default function CodePage({ params }: { params: { code: string } }) { }; }, [code]); - // 현재가/전일가 + 헤더 sparkline 시리즈 — ohlcv 끝부분에서 산출. - const { current, prev, asOf, sparkSeries } = useMemo(() => { - if (!chart || !chart.ohlcv.length) - return { current: null, prev: null, asOf: null, sparkSeries: [] as number[] }; + // 현재가/전일가 + 헤더 sparkline 시리즈 + 오늘 흐름 OHLV — ohlcv 끝부분에서 산출. + // + // flow 의 정의 (period 모드별로 "오늘" 의 의미가 다름): + // - 1D(10m): 같은 KST 날짜의 모든 봉을 묶어서 today 의 세션 OHLV 로 집계. + // open = 첫 봉 open, high = max(high), low = min(low), volume = sum(volume). + // - 1d/1w/1mo: 마지막 봉 자체가 한 단위 (일/주/월) — 그 봉의 OHLV 를 그대로 사용. + // PriceHero hero 라인 아래에 "시 / 고 / 저 / 거래량" mini KPI 로 표시 — 차트 hover 없이도 + // 페이지 진입 즉시 그날 흐름이 한 줄로 보임. + const { current, prev, asOf, sparkSeries, flow } = useMemo(() => { + const empty = { + current: null as number | null, + prev: null as number | null, + asOf: null as string | null, + sparkSeries: [] as number[], + flow: null as { open: number; high: number; low: number; volume: number } | null, + }; + if (!chart || !chart.ohlcv.length) return empty; const valid = chart.ohlcv.filter((p) => p.close != null); - if (!valid.length) - return { current: null, prev: null, asOf: null, sparkSeries: [] as number[] }; + if (!valid.length) return empty; const last = valid[valid.length - 1]; const prevPt = valid.length >= 2 ? valid[valid.length - 2] : null; // 최근 30개 종가 (없으면 가용 전체). const recent = valid.slice(-30).map((p) => p.close as number); + + // flow 집계. + const isIntra = chart.interval === "10m"; + let bars: typeof valid; + if (isIntra) { + // 10m 모드는 'YYYY-MM-DDTHH:MM:SS' — 'T' 이전이 KST 날짜. 마지막 봉의 날짜와 같은 + // 봉만 묶음 (intraday 세션 = 오늘). + const lastDay = last.date.split("T")[0]; + bars = valid.filter((p) => p.date.split("T")[0] === lastDay); + } else { + bars = [last]; + } + const opens = bars.map((p) => p.open).filter((v): v is number => v != null); + const highs = bars.map((p) => p.high).filter((v): v is number => v != null); + const lows = bars.map((p) => p.low).filter((v): v is number => v != null); + const vols = bars.map((p) => p.volume).filter((v): v is number => v != null); + const flow = + opens.length && highs.length && lows.length + ? { + open: opens[0], + high: Math.max(...highs), + low: Math.min(...lows), + // volume 누락 봉은 0 로 취급 (sum 안 그러면 NaN). 1d 모드는 단일 봉이라 무관. + volume: vols.reduce((a, b) => a + b, 0), + } + : null; + return { current: last.close, prev: prevPt?.close ?? null, asOf: last.date, sparkSeries: recent, + flow, }; }, [chart]); @@ -238,6 +278,7 @@ export default function CodePage({ params }: { params: { code: string } }) { asOf={asOf} series={sparkSeries} viewsToday={viewsToday} + flow={flow} />