diff --git a/src/pages/stock/Stock.css b/src/pages/stock/Stock.css index b5fce04..464333c 100644 --- a/src/pages/stock/Stock.css +++ b/src/pages/stock/Stock.css @@ -1371,6 +1371,56 @@ AI 투자 코치 패널 ══════════════════════════════════════════════════════════════════ */ +.ai-market-ctx { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 16px; + background: rgba(0, 212, 255, 0.04); + border: 1px solid rgba(0, 212, 255, 0.15); + border-radius: var(--radius-sm); + margin-bottom: 4px; +} + +.ai-market-ctx__label { + font-size: 10px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--neon-cyan); + white-space: nowrap; + padding-top: 2px; +} + +.ai-market-ctx__chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.ai-market-chip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--line); + border-radius: 100px; + font-size: 11px; + color: var(--text-dim); +} + +.ai-market-chip strong { + color: var(--text-bright); + font-weight: 700; +} + +.ai-market-chip em { + font-style: normal; + font-size: 10px; + color: var(--text-muted); +} + .ai-coach-settings { display: grid; grid-template-columns: 1fr auto; diff --git a/src/pages/stock/StockTrade.jsx b/src/pages/stock/StockTrade.jsx index 83d5270..12bc7d6 100644 --- a/src/pages/stock/StockTrade.jsx +++ b/src/pages/stock/StockTrade.jsx @@ -9,6 +9,10 @@ import { deletePortfolio, upsertCash, deleteCash, + getFearAndGreed, + getVix, + getTreasury10Y, + getWTI, } from '../../api'; import Loading from '../../components/Loading'; import './Stock.css'; @@ -91,6 +95,22 @@ const profitColorClass = (numericValue) => { return ''; }; +const getVixLabel = (vix) => { + if (vix < 12) return '극히 낮음 (안일 주의)'; + if (vix < 20) return '정상 (안정적)'; + if (vix < 30) return '주의 (불확실성 증가)'; + if (vix < 40) return '높음 (극도의 공포)'; + return '극단 (패닉)'; +}; + +const getFgLabel = (score) => { + if (score <= 25) return '극단적 공포'; + if (score <= 45) return '공포'; + if (score <= 55) return '중립'; + if (score <= 75) return '탐욕'; + return '극단적 탐욕'; +}; + /* ── empty portfolio form ────────────────────────────────────────── */ const emptyPortfolioForm = { @@ -153,6 +173,7 @@ const StockTrade = () => { const [aiResult, setAiResult] = useState(null); const [aiLoading, setAiLoading] = useState(false); const [aiError, setAiError] = useState(''); + const [marketCtx, setMarketCtx] = useState(null); /* ────────────────────────────────────────────────────────────── */ /* AI 투자 (Balance) state */ @@ -228,6 +249,22 @@ const StockTrade = () => { } }, []); + /* 리포트 탭 진입 시 시장 컨텍스트(VIX, F&G, 국채, WTI) 한 번 로드 */ + useEffect(() => { + if (activeTab !== TAB_REPORT || marketCtx !== null) return; + Promise.allSettled([getFearAndGreed(), getVix(), getTreasury10Y(), getWTI()]) + .then(([fg, vix, t, w]) => { + const fgRaw = fg.status === 'fulfilled' ? fg.value : null; + const fgScore = fgRaw?.fear_and_greed?.score ?? fgRaw?.score; + setMarketCtx({ + fg: fgScore != null ? Math.round(Number(fgScore)) : null, + vix: vix.status === 'fulfilled' ? (vix.value?.value ?? null) : null, + treasury: t.status === 'fulfilled' ? (t.value?.value ?? null) : null, + wti: w.status === 'fulfilled' ? (w.value?.value ?? null) : null, + }); + }); + }, [activeTab, marketCtx]); + /* Auto-refresh portfolio every 3 min (포트폴리오 탭 활성 시) */ useEffect(() => { if (activeTab !== TAB_PORTFOLIO) return; @@ -381,7 +418,11 @@ const StockTrade = () => { ) .join('\n'); - const prompt = `당신은 한국 주식 전문 투자 코치입니다. 아래 포트폴리오를 분석하여 JSON으로만 답하세요. + const marketText = marketCtx + ? `\n[현재 시장 환경]\nVIX: ${marketCtx.vix != null ? `${marketCtx.vix} (${getVixLabel(marketCtx.vix)})` : '데이터 없음'}\nFear & Greed: ${marketCtx.fg != null ? `${marketCtx.fg}점 (${getFgLabel(marketCtx.fg)})` : '데이터 없음'}\n미국 10년물 국채: ${marketCtx.treasury != null ? `${marketCtx.treasury}%` : '데이터 없음'}\nWTI 유가: ${marketCtx.wti != null ? `$${marketCtx.wti}` : '데이터 없음'}` + : ''; + + const prompt = `당신은 한국 주식 전문 투자 코치입니다. 아래 포트폴리오와 시장 환경을 종합 분석하여 JSON으로만 답하세요. 분석 일자: ${today} 총 매입금액: ${formatNumber(portfolioSummary.total_buy)}원 @@ -391,7 +432,7 @@ const StockTrade = () => { 총 자산: ${totalAssets != null ? formatNumber(totalAssets) + '원' : '미집계'} 보유 종목 수: ${portfolioHoldings.length}개 보유 종목: -${holdingsText} +${holdingsText}${marketText} 반드시 아래 JSON 형식으로만 응답하세요 (코드블록 없이, 모든 텍스트는 한국어로): { @@ -1695,6 +1736,37 @@ ${holdingsText} + {/* 시장 컨텍스트 미니 패널 */} + {marketCtx && ( +
+ 시장 환경 +
+ {marketCtx.vix != null && ( + + VIX {marketCtx.vix} + {getVixLabel(marketCtx.vix)} + + )} + {marketCtx.fg != null && ( + + F&G {marketCtx.fg} + {getFgLabel(marketCtx.fg)} + + )} + {marketCtx.treasury != null && ( + + 10년물 {marketCtx.treasury}% + + )} + {marketCtx.wti != null && ( + + WTI ${marketCtx.wti} + + )} +
+
+ )} + {/* API Key 설정 */}