diff --git a/web/app/[code]/page.tsx b/web/app/[code]/page.tsx
index 1931eb7..091b65c 100644
--- a/web/app/[code]/page.tsx
+++ b/web/app/[code]/page.tsx
@@ -5,6 +5,7 @@ import { useEffect, useMemo, useState } from "react";
import { DisclosuresPanel } from "../../components/DisclosuresPanel";
import { MetricsPanel } from "../../components/MetricsPanel";
import { NewsList } from "../../components/NewsList";
+import { PeriodReturns } from "../../components/PeriodReturns";
import { PeriodTabs, periodSpec, type Period } from "../../components/PeriodTabs";
import { PredictionPanel } from "../../components/PredictionPanel";
import { PriceHero } from "../../components/PriceHero";
@@ -12,6 +13,7 @@ import { RelatedStocks } from "../../components/RelatedStocks";
import { StarButton } from "../../components/StarButton";
import { StockChart } from "../../components/StockChart";
import { SymbolSidebar } from "../../components/SymbolSidebar";
+import { TradingValuePanel } from "../../components/TradingValuePanel";
import { api, type ChartPayload, type LatestPredictionResponse } from "../../lib/api";
export default function CodePage({ params }: { params: { code: string } }) {
@@ -138,11 +140,15 @@ export default function CodePage({ params }: { params: { code: string } }) {
-
-
-
-
+
+
+
+
+
+
>
diff --git a/web/components/PeriodReturns.tsx b/web/components/PeriodReturns.tsx
new file mode 100644
index 0000000..1b9d432
--- /dev/null
+++ b/web/components/PeriodReturns.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+// 기간별 수익률 패널 — chart.ohlcv 에서 직접 계산.
+// 토스 종목 페이지의 "기간 수익률" 박스 모사. 1주/1개월/3개월/6개월/1년/전체.
+// 거래일 기준 근사: 1주≈5, 1개월≈21, 3개월≈63, 6개월≈126, 1년≈252.
+
+import type { OhlcvPoint } from "../lib/api";
+
+const BUCKETS: { label: string; days: number }[] = [
+ { label: "1주", days: 5 },
+ { label: "1개월", days: 21 },
+ { label: "3개월", days: 63 },
+ { label: "6개월", days: 126 },
+ { label: "1년", days: 252 },
+];
+
+export function PeriodReturns({ ohlcv }: { ohlcv: OhlcvPoint[] }) {
+ const closes = ohlcv
+ .map((p) => p.close)
+ .filter((c): c is number => c != null);
+ if (closes.length < 2) {
+ return (
+
+ 기간 수익률 데이터 없음
+
+ );
+ }
+ const last = closes[closes.length - 1];
+ const buckets = BUCKETS.map((b) => {
+ const idx = closes.length - 1 - b.days;
+ if (idx < 0) return { ...b, pct: null as number | null };
+ const base = closes[idx];
+ return { ...b, pct: base ? ((last - base) / base) * 100 : null };
+ });
+ const all =
+ closes[0] != null && closes[0] !== 0
+ ? ((last - closes[0]) / closes[0]) * 100
+ : null;
+
+ return (
+
+
+
+ {buckets.map((b) => (
+
+ ))}
+
+
+
+ );
+}
+
+function Bucket({ label, pct }: { label: string; pct: number | null }) {
+ const tone =
+ pct == null || pct === 0
+ ? "text-zinc-400"
+ : pct > 0
+ ? "text-rose-400"
+ : "text-sky-400";
+ return (
+
+
{label}
+
+ {pct == null
+ ? "—"
+ : `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`}
+
+
+ );
+}
diff --git a/web/components/TradingValuePanel.tsx b/web/components/TradingValuePanel.tsx
new file mode 100644
index 0000000..46c49a2
--- /dev/null
+++ b/web/components/TradingValuePanel.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+// 거래주체 패널 — chart.trading_value (외국인/기관/개인 순매수) 를 최근 5일 표로.
+// 별도 백엔드 호출 없음. ChartPayload 에 이미 들어있는 시리즈를 재사용.
+// 단위: 백엔드는 원(KRW) 단위. UI 는 억원(억원=1e8)으로 환산.
+// 색: KR 관행 — 매수 우위(+) 빨강, 매도 우위(-) 파랑.
+
+import type { TradingValuePoint } from "../lib/api";
+
+export function TradingValuePanel({
+ data,
+}: {
+ data: TradingValuePoint[];
+}) {
+ if (!data || data.length === 0) {
+ return (
+
+ 거래주체 데이터 없음
+
+ );
+ }
+ // 최근 5일만 (입력은 오래된→최신 순), 표시는 최신이 위.
+ const recent = data.slice(-5).reverse();
+
+ return (
+
+
+
거래주체 순매수
+
최근 {recent.length}일 · 단위: 억원
+
+
+
+
+
+ | 일자 |
+ 외국인 |
+ 기관 |
+ 개인 |
+
+
+
+ {recent.map((row) => (
+
+ |
+ {shortDate(row.date)}
+ |
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+function NetCell({ value }: { value: number | null }) {
+ if (value == null) {
+ return — | ;
+ }
+ const eok = value / 1e8;
+ const tone =
+ eok === 0
+ ? "text-zinc-400"
+ : eok > 0
+ ? "text-rose-400"
+ : "text-sky-400";
+ const sign = eok > 0 ? "+" : "";
+ return (
+
+ {sign}
+ {eok.toLocaleString(undefined, { maximumFractionDigits: 0 })}
+ |
+ );
+}
+
+function shortDate(iso: string): string {
+ // 'YYYY-MM-DD' → 'MM-DD'
+ const m = /^\d{4}-(\d{2})-(\d{2})/.exec(iso);
+ return m ? `${m[1]}-${m[2]}` : iso;
+}