시장 주요 지표 참고 추가

This commit is contained in:
2026-03-05 02:45:45 +09:00
parent ccc9f7c634
commit c28bd9368c
4 changed files with 372 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { getStockIndices, getStockNews, getFearAndGreed, getVix } from '../../api';
import { getStockIndices, getStockNews, getFearAndGreed, getVix, getTreasury10Y, getWTI, getBrent } from '../../api';
import Loading from '../../components/Loading';
import FearGreedGauge from '../../components/FearGreedGauge';
import './Stock.css';
@@ -77,12 +77,35 @@ const getDirection = (change, percent, direction) => {
return '';
};
const VIX_LEVELS = [
{
range: '0 12', label: '극히 낮음', color: '#22c55e',
desc: '시장이 극도로 안정적. 오히려 투자자 안일함의 신호일 수 있어, 갑작스러운 조정에 대비가 필요합니다.',
},
{
range: '12 20', label: '정상', color: '#84cc16',
desc: '시장이 안정적인 상태. 보통 상승장에서 나타나며, 건강한 변동성 수준입니다.',
},
{
range: '20 30', label: '주의', color: '#eab308',
desc: '불확실성이 높아지는 구간. 주가와 반대로 움직이며, 단기 바닥 신호로 해석되기도 합니다.',
},
{
range: '30 40', label: '높음', color: '#f97316',
desc: '극도의 공포가 퍼진 상태. 급격한 매도세가 나타나지만, 역사적으로 역발상 매수 기회가 되기도 합니다.',
},
{
range: '40+', label: '극단', color: '#ef4444',
desc: '패닉 수준의 공포. 2008 금융위기·2020 코로나 때 발생. VIX가 꺾이기 시작하면 심리적 진정의 시작입니다.',
},
];
const getVixLevel = (score) => {
if (score < 12) return { label: '극히 낮음', color: '#22c55e' };
if (score < 20) return { label: '정상', color: '#84cc16' };
if (score < 30) return { label: '보통', color: '#eab308' };
if (score < 40) return { label: '높음', color: '#f97316' };
return { label: '극단', color: '#ef4444' };
if (score < 12) return VIX_LEVELS[0];
if (score < 20) return VIX_LEVELS[1];
if (score < 30) return VIX_LEVELS[2];
if (score < 40) return VIX_LEVELS[3];
return VIX_LEVELS[4];
};
const Stock = () => {
@@ -99,6 +122,7 @@ const Stock = () => {
const [fgData, setFgData] = useState(null);
const [vixData, setVixData] = useState(null);
const [macroData, setMacroData] = useState({ treasury: null, wti: null, brent: null });
const combinedNews = useMemo(
() => [...newsDomestic, ...newsOverseas],
@@ -156,6 +180,14 @@ const Stock = () => {
})
.catch(() => { });
getVix().then(setVixData).catch(() => { });
Promise.allSettled([getTreasury10Y(), getWTI(), getBrent()])
.then(([t, w, b]) => {
setMacroData({
treasury: t.status === 'fulfilled' ? t.value : null,
wti: w.status === 'fulfilled' ? w.value : null,
brent: b.status === 'fulfilled' ? b.value : null,
});
});
}, []);
const indexOrder = [
@@ -307,18 +339,36 @@ const Stock = () => {
</div>
{vixData ? (
<div className="stock-vix">
<div className="stock-vix__score" style={{ color: getVixLevel(vixData.value ?? vixData.vix ?? 0).color }}>
{vixData.value ?? vixData.vix ?? '--'}
<div className="stock-vix__top">
<div className="stock-vix__score" style={{ color: getVixLevel(vixData.value ?? 0).color }}>
{vixData.value ?? '--'}
</div>
<div>
<p className="stock-vix__label" style={{ color: getVixLevel(vixData.value ?? 0).color }}>
{getVixLevel(vixData.value ?? 0).label}
</p>
{vixData.change != null && (
<p className={`stock-vix__change ${vixData.change >= 0 ? 'is-up' : 'is-down'}`}>
{vixData.change >= 0 ? '+' : ''}{vixData.change}
{vixData.changePercent != null && ` (${vixData.changePercent >= 0 ? '+' : ''}${vixData.changePercent}%)`}
</p>
)}
</div>
</div>
<p className="stock-vix__label" style={{ color: getVixLevel(vixData.value ?? vixData.vix ?? 0).color }}>
{getVixLevel(vixData.value ?? vixData.vix ?? 0).label}
</p>
<div className="stock-vix__legend">
<span style={{ color: '#22c55e' }}>{'<12'} 극히낮음</span>
<span style={{ color: '#84cc16' }}>12-20 정상</span>
<span style={{ color: '#eab308' }}>20-30 보통</span>
<span style={{ color: '#f97316' }}>30-40 높음</span>
<span style={{ color: '#ef4444' }}>{'40+'} 극단</span>
<div className="stock-vix__levels">
{VIX_LEVELS.map((level) => (
<div
key={level.range}
className={`stock-vix__level ${level.label === getVixLevel(vixData.value ?? 0).label ? 'is-current' : ''}`}
>
<div className="stock-vix__level-head">
<span className="stock-vix__level-dot" style={{ background: level.color }} />
<span className="stock-vix__level-label" style={{ color: level.color }}>{level.label}</span>
<span className="stock-vix__level-range">{level.range}</span>
</div>
<p className="stock-vix__level-desc">{level.desc}</p>
</div>
))}
</div>
</div>
) : (
@@ -327,6 +377,83 @@ const Stock = () => {
</div>
</section>
{/* 매크로 지표 섹션 */}
<section className="stock-panel stock-panel--wide">
<div className="stock-panel__head">
<div>
<p className="stock-panel__eyebrow">글로벌 매크로</p>
<h3>매크로 지표</h3>
<p className="stock-panel__sub">금리·원자재 주요 거시경제 지표를 확인합니다.</p>
</div>
</div>
<div className="stock-macro-grid">
<div className="stock-macro-card">
<p className="stock-macro-card__title">미국 10년물 국채 금리</p>
<div className="stock-macro-card__value">
{macroData.treasury ? `${macroData.treasury.value}%` : '--'}
</div>
{macroData.treasury?.change != null && (
<p className={`stock-macro-card__change ${macroData.treasury.change >= 0 ? 'is-up' : 'is-down'}`}>
{macroData.treasury.change >= 0 ? '+' : ''}{macroData.treasury.change}
{macroData.treasury.changePercent != null && ` (${macroData.treasury.changePercent >= 0 ? '+' : ''}${macroData.treasury.changePercent}%)`}
</p>
)}
<p className="stock-macro-card__desc">금리 상승 주식 밸류에이션 압박. 4% 이상 지속은 주식 하락 압력 신호. 단기 급등은 인플레이션 우려를 반영합니다.</p>
</div>
<div className="stock-macro-card">
<p className="stock-macro-card__title">WTI 유가</p>
<div className="stock-macro-card__value">
{macroData.wti ? `$${macroData.wti.value}` : '--'}
</div>
{macroData.wti?.change != null && (
<p className={`stock-macro-card__change ${macroData.wti.change >= 0 ? 'is-up' : 'is-down'}`}>
{macroData.wti.change >= 0 ? '+' : ''}{macroData.wti.change}
{macroData.wti.changePercent != null && ` (${macroData.wti.changePercent >= 0 ? '+' : ''}${macroData.wti.changePercent}%)`}
</p>
)}
<p className="stock-macro-card__desc">에너지 인플레이션 지표. $80 이상 지속 물가 상승 우려 확대. 급락은 경기침체 가능성을 반영하기도 합니다.</p>
</div>
<div className="stock-macro-card">
<p className="stock-macro-card__title">Brent 유가</p>
<div className="stock-macro-card__value">
{macroData.brent ? `$${macroData.brent.value}` : '--'}
</div>
{macroData.brent?.change != null && (
<p className={`stock-macro-card__change ${macroData.brent.change >= 0 ? 'is-up' : 'is-down'}`}>
{macroData.brent.change >= 0 ? '+' : ''}{macroData.brent.change}
{macroData.brent.changePercent != null && ` (${macroData.brent.changePercent >= 0 ? '+' : ''}${macroData.brent.changePercent}%)`}
</p>
)}
<p className="stock-macro-card__desc">국제 기준 유가. WTI와 함께 에너지 시장 방향을 파악하는 활용. 지정학 리스크 WTI 대비 프리미엄 형성.</p>
</div>
</div>
</section>
{/* 시장 건강 지표 (Placeholder) */}
<section className="stock-panel stock-panel--wide">
<div className="stock-panel__head">
<div>
<p className="stock-panel__eyebrow">시장 건강</p>
<h3>시장 건강 지표</h3>
<p className="stock-panel__sub">백엔드 API 연동 실시간 데이터를 표시합니다.</p>
</div>
</div>
<div className="stock-health-grid">
<div className="stock-placeholder-card">
<p className="stock-placeholder-card__title">ADR (등락주선 비율)</p>
<div className="stock-placeholder-card__status">🔧 데이터 준비 </div>
<p className="stock-placeholder-card__desc">일정 기간 상승종목 ÷ (상승+하락) 종목 비율. 0.5 이상 = 폭넓은 상승장. 0.3 이하 = 일부 대형주만 오르는 약세 신호.</p>
<code className="stock-placeholder-card__api">GET /api/stock/adr</code>
</div>
<div className="stock-placeholder-card">
<p className="stock-placeholder-card__title">고객예탁금 / 신용융자</p>
<div className="stock-placeholder-card__status">🔧 데이터 준비 </div>
<p className="stock-placeholder-card__desc">고객예탁금 증가 = 투자 대기자금 유입 = 강세. 신용융자 급증 = 과열 경고. 예탁금 감소 + 신용 급증 = 위험 구간.</p>
<code className="stock-placeholder-card__api">GET /api/stock/deposit</code>
</div>
</div>
</section>
<section className="stock-panel stock-panel--wide">
<div className="stock-panel__head">
<div>