Macro Dashboard
거시경제 레짐 분석
regimeColors = ({
"Goldilocks": "#2ecc71",
"과열": "#e67e22",
"침체/디스인플레": "#3498db",
"스태그플레이션": "#e74c3c",
"전환기": "#95a5a6"
})
directionArrow = (d) => ({
rising: "▲", falling: "▼", stable: "→",
tightening: "▲ 긴축", holding: "→ 유지", easing: "▼ 완화"
})[d] || ""
fmtNum = (v, d = 1) => v == null ? "—" : Number(v).toFixed(d)
fmtComma = (v) => v == null ? "—" : Number(v).toLocaleString("en-US", {
minimumFractionDigits: 1, maximumFractionDigits: 1
}){
const color = regimeColors[regime.regime_label] || "#95a5a6";
return html`
<div style="background:#1a1d2e; border:1px solid #2d3148; border-radius:12px; padding:24px; margin-bottom:8px;">
<div style="display:flex; align-items:center; gap:16px; flex-wrap:wrap;">
<span style="background:${color}; color:#000; font-weight:700; font-size:1.3rem;
padding:6px 20px; border-radius:24px;">${regime.regime_label}</span>
<span style="color:#8892b0; font-size:0.9rem;">${regime.snapshot_date}</span>
</div>
<p style="color:#c8cce0; margin-top:12px; line-height:1.7; font-size:0.95rem;">
${regime.regime_summary}
</p>
</div>
`;
}4축 스코어
{
const axes = [
{ label: "성장 Growth", score: regime.growth_score, dir: regime.growth_direction,
color: "#2ecc71", min: 0, max: 100 },
{ label: "물가 Inflation", score: regime.inflation_score, dir: regime.inflation_direction,
color: "#e67e22", min: 0, max: 100 },
{ label: "통화정책 Policy", score: regime.policy_score, dir: regime.policy_direction,
color: "#9b59b6", min: -100, max: 100 },
{ label: "리스크 선호 Risk", score: regime.risk_score, dir: null,
color: "#3498db", min: 0, max: 100 },
];
return html`
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(260px, 1fr)); gap:12px;">
${axes.map(a => {
const pct = ((a.score - a.min) / (a.max - a.min)) * 100;
const dirText = a.dir ? directionArrow(a.dir) : "";
return html`
<div style="background:#1a1d2e; border:1px solid #2d3148; border-radius:8px; padding:16px;">
<div style="display:flex; justify-content:space-between; margin-bottom:6px;">
<span style="color:#8892b0; font-size:0.85rem;">${a.label}</span>
<span style="font-weight:700; font-size:0.95rem;">
${a.min < 0 ? (a.score > 0 ? "+" : "") : ""}${fmtNum(a.score)}
<span style="color:#8892b0; font-size:0.8rem; margin-left:4px;">${dirText}</span>
</span>
</div>
<div style="background:#2d3148; border-radius:4px; height:10px; overflow:hidden;">
<div style="height:100%; width:${pct}%; background:${a.color}; border-radius:4px;
transition:width 0.3s;"></div>
</div>
</div>
`;
})}
</div>
`;
}주요 지표
{
if (!regime.indicators) return html`<p style="color:#8892b0;">지표 데이터 없음</p>`;
const ind = regime.indicators;
const groups = [
{ title: "금리 & 정책", items: [
["연준 기준금리", ind.DFF, "%"],
["한국 기준금리", ind.KOR_BASE_RATE, "%"],
["미 10Y 국채", ind.DGS10, "%"],
["장단기 스프레드", ind.T10Y2Y, "%p"],
["실질금리 10Y", ind.DFII10, "%"],
["기대인플레 5Y", ind.T5YIE, "%"],
["NFCI", ind.NFCI, ""],
]},
{ title: "리스크 & 변동성", items: [
["VIX", ind.VIXCLS, ""],
["IG 크레딧 스프레드", ind.BAMLC0A0CMEY, "%"],
["HY 크레딧 스프레드", ind.BAMLH0A0HYM2, "%"],
]},
{ title: "환율 & 원자재", items: [
["원/달러", ind.KRW_X || ind.DEXKOUS_ECOS, "₩"],
["달러인덱스", ind.DTWEXBGS, ""],
["WTI 원유", ind.DCOILWTICO, "$/bbl"],
["브렌트 원유", ind.DCOILBRENTEU, "$/bbl"],
["금 선물", ind.GC_F, "$/oz"],
["구리 선물", ind.HG_F, "$/lb"],
]},
{ title: "실물경제", items: [
["중국 제조업 PMI", ind.CHN_MANUFACTURING_PMI, ""],
["한국 수출", ind.KOR_EXPORTS_TOTAL, "백만$"],
["투자자 예탁금", ind.KOR_INVESTOR_DEPOSIT, "억원"],
]},
{ title: "시장", items: [
["S&P 500", ind.GSPC, ""],
["KOSPI", ind.KS11, ""],
["KOSDAQ", ind.KQ11, ""],
["MSCI EM", ind.EEM, ""],
]},
];
return html`
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); gap:12px;">
${groups.map(g => html`
<div style="background:#1a1d2e; border:1px solid #2d3148; border-radius:8px; padding:16px;">
<div style="color:#64ffda; font-size:0.8rem; font-weight:600; text-transform:uppercase;
letter-spacing:0.05em; margin-bottom:10px;">${g.title}</div>
<table style="width:100%; border-collapse:collapse; font-size:0.85rem;">
${g.items.filter(([,v]) => v != null).map(([name, val, unit]) => html`
<tr style="border-bottom:1px solid #1e2235;">
<td style="padding:4px 0; color:#c8cce0;">${name}</td>
<td style="padding:4px 0; text-align:right; font-variant-numeric:tabular-nums;">
${fmtComma(val)} <span style="color:#8892b0; font-size:0.75rem;">${unit}</span>
</td>
</tr>
`)}
</table>
</div>
`)}
</div>
`;
}섹터 리바운드 기회
{
const sectors = regime.sector_scores;
if (!sectors || sectors.length === 0)
return html`<p style="color:#8892b0;">섹터 데이터 없음</p>`;
return html`
<div style="background:#1a1d2e; border:1px solid #2d3148; border-radius:8px; padding:16px; overflow-x:auto;">
<table style="width:100%; border-collapse:collapse; font-size:0.85rem; min-width:600px;">
<thead>
<tr style="border-bottom:2px solid #2d3148;">
<th style="text-align:left; padding:8px; color:#8892b0;">#</th>
<th style="text-align:left; padding:8px; color:#8892b0;">섹터</th>
<th style="text-align:right; padding:8px; color:#8892b0;">리바운드</th>
<th style="text-align:right; padding:8px; color:#8892b0;">방치도</th>
<th style="text-align:right; padding:8px; color:#8892b0;">환율쿨링</th>
<th style="text-align:right; padding:8px; color:#8892b0;">20일 초과수익</th>
<th style="text-align:right; padding:8px; color:#8892b0;">Z-Score</th>
</tr>
</thead>
<tbody>
${sectors.map((s, i) => {
const reboundColor = s.rebound_score >= 70 ? "#2ecc71"
: s.rebound_score >= 50 ? "#e67e22" : "#e8eaf6";
return html`
<tr style="border-bottom:1px solid #1e2235;">
<td style="padding:6px 8px; color:#8892b0;">${i + 1}</td>
<td style="padding:6px 8px;">${s.idx_nm}</td>
<td style="padding:6px 8px; text-align:right; font-weight:700; color:${reboundColor};">
${fmtNum(s.rebound_score)}
</td>
<td style="padding:6px 8px; text-align:right;">${fmtNum(s.neglect_score)}</td>
<td style="padding:6px 8px; text-align:right;">${fmtNum(s.cooling_score)}</td>
<td style="padding:6px 8px; text-align:right; color:${s.recent_excess_20d >= 0 ? '#2ecc71' : '#e74c3c'};">
${s.recent_excess_20d >= 0 ? "+" : ""}${fmtNum(s.recent_excess_20d, 2)}%
</td>
<td style="padding:6px 8px; text-align:right;">${fmtNum(s.zscore, 2)}</td>
</tr>
`;
})}
</tbody>
</table>
<p style="color:#8892b0; font-size:0.75rem; margin-top:10px;">
리바운드 = 방치도 x 0.6 + 환율쿨링 x 0.4 |
방치도: 최근 소외 섹터일수록 높음 |
환율쿨링: 달러 약세 시 아웃퍼폼 경향
</p>
</div>
`;
}지표 추이
// Group indicator time series by series_id
indicatorSeries = {
const grouped = {};
for (const row of indicators) {
if (!grouped[row.series_id]) grouped[row.series_id] = [];
grouped[row.series_id].push({
date: new Date(row.prd_de.slice(0,4) + "-" + row.prd_de.slice(4,6) + "-" + row.prd_de.slice(6,8)),
value: +row.data_value
});
}
return grouped;
}Plot.plot({
width: Math.min(800, window.innerWidth - 60),
height: 280,
style: { background: "#1a1d2e", color: "#c8cce0", fontSize: "12px" },
x: { type: "time", label: null },
y: { label: seriesLabels[pickedSeries] || pickedSeries, grid: true },
marks: [
Plot.ruleY([0], { stroke: "#2d3148" }),
Plot.lineY(indicatorSeries[pickedSeries] || [], {
x: "date", y: "value", stroke: "#64ffda", strokeWidth: 1.5
}),
Plot.dot(indicatorSeries[pickedSeries]?.slice(-1) || [], {
x: "date", y: "value", fill: "#64ffda", r: 4
}),
]
})글로벌 지수 추이
marketSeries = {
const grouped = {};
for (const row of markets) {
if (!grouped[row.series_id]) grouped[row.series_id] = [];
grouped[row.series_id].push({
date: new Date(row.prd_de.slice(0,4) + "-" + row.prd_de.slice(4,6) + "-" + row.prd_de.slice(6,8)),
value: +row.data_value
});
}
return grouped;
}
marketLabels = ({
GSPC: "S&P 500",
IXIC: "NASDAQ",
KS11: "KOSPI",
KQ11: "KOSDAQ",
N225: "Nikkei 225",
HSI: "Hang Seng",
STOXX50E: "Euro Stoxx 50",
GC_F: "Gold ($/oz)",
HG_F: "Copper ($/lb)",
EEM: "MSCI EM ETF",
})Plot.plot({
width: Math.min(800, window.innerWidth - 60),
height: 280,
style: { background: "#1a1d2e", color: "#c8cce0", fontSize: "12px" },
x: { type: "time", label: null },
y: { label: marketLabels[pickedMarket] || pickedMarket, grid: true },
marks: [
Plot.lineY(marketSeries[pickedMarket] || [], {
x: "date", y: "value", stroke: "#e67e22", strokeWidth: 1.5
}),
Plot.dot(marketSeries[pickedMarket]?.slice(-1) || [], {
x: "date", y: "value", fill: "#e67e22", r: 4
}),
]
})본 대시보드는 자동 생성된 정량 분석이며 투자 권유가 아닙니다. 데이터 갱신: weekly~monthly.