diff --git a/web/components/StockChart.tsx b/web/components/StockChart.tsx index 67fd462..bb43c6a 100644 --- a/web/components/StockChart.tsx +++ b/web/components/StockChart.tsx @@ -161,6 +161,7 @@ export function StockChart({ chart, prediction }: Props) { const containerRef = useRef(null); const chartRef = useRef(null); const candleRef = useRef | null>(null); + const volumeRef = useRef | null>(null); const predRef = useRef | null>(null); const smaRefs = useRef>>(new Map()); // RSI 보조차트 — 별도 chart 인스턴스로 메인 아래에 붙여 timeScale 동기화. @@ -213,13 +214,30 @@ export function StockChart({ chart, prediction }: Props) { wickUpColor: COLOR_UP, wickDownColor: COLOR_DOWN, }); + // 캔들 priceScale 을 위쪽 78% 로 좁혀서 거래량 막대를 아래쪽에 둘 공간 확보. + c.priceScale("right").applyOptions({ + scaleMargins: { top: 0.05, bottom: 0.22 }, + }); + // 거래량 막대 — overlay priceScale("volume") 로 같은 chart 안에 띄움. + // bottom 0/top 0.82 → 차트 영역의 아래 18% 만 사용. 가격축 라벨은 숨김. + const volume = c.addHistogramSeries({ + priceFormat: { type: "volume" }, + priceScaleId: "volume", + priceLineVisible: false, + lastValueVisible: false, + }); + c.priceScale("volume").applyOptions({ + scaleMargins: { top: 0.82, bottom: 0 }, + }); chartRef.current = c; candleRef.current = candle; + volumeRef.current = volume; const smaMap = smaRefs.current; return () => { c.remove(); chartRef.current = null; candleRef.current = null; + volumeRef.current = null; predRef.current = null; smaMap.clear(); }; @@ -241,6 +259,23 @@ export function StockChart({ chart, prediction }: Props) { chartRef.current?.timeScale().fitContent(); }, [chart, isIntraday]); + // 거래량 막대 — 종가 ≥ 시가면 한국 관행으로 빨강(상승), 아니면 파랑(하락). + // null volume 은 그 시점 캔들 자체가 빠진 케이스(상장폐지/거래정지) 라 건너뛴다. + useEffect(() => { + if (!volumeRef.current) return; + const data: HistogramData[] = chart.ohlcv + .filter((p) => p.volume != null && p.volume > 0) + .map((p) => { + const up = p.close != null && p.open != null ? p.close >= p.open : true; + return { + time: isoToUtcTs(p.date), + value: p.volume as number, + color: up ? "#ef444466" : "#3b82f666", + }; + }); + volumeRef.current.setData(data); + }, [chart, isIntraday]); + // SMA 시리즈 — chart.ohlcv 종가 기준. 토글 변화 또는 데이터 변화 시 갱신. // 비활성 윈도우는 제거하고, 활성은 재계산. 마지막에 fitContent 는 호출 안 함 — 사용자 줌 유지. const closes = useMemo(() => chart.ohlcv.map((p) => p.close), [chart]);