(2/2) 거시 지표 대시보드 + 발표 서프라이즈 — 그래프로 엮고 예상 대비 실제 계산
📈 미국 거시 지표 Python 시리즈 (전체 2편)
- 거시 지표 — 어디서 보고 Python 으로 가져오나
- 대시보드 + 발표 서프라이즈 — 그래프로 엮고 예상 대비 실제 계산 ← 지금 글
Summary
직전 글 에서는 CPI·PCE·실업률·기준금리·장단기 금리차·VIX 를 FRED 와 yfinance 로 받아오는 패턴을 정리했어요. 받는 것까진 됐는데, 숫자만 print 해서는 흐름이 잘 안 보이죠. 그래서 이번 편은 그 다음 두 가지예요.
하나는 대시보드 — 받은 지표를 matplotlib 으로 한 화면에 그려서 추세를 한눈에 보는 거예요. 침체기 음영까지 입히면 “이 지표가 침체 전후로 어떻게 움직였나” 가 바로 보여요.
다른 하나는 발표 서프라이즈 — 시장이 실제로 반응하는 건 지표의 값 자체가 아니라 “예상치 대비 얼마나 벗어났나” 예요. CPI 가 3% 나와도, 예상이 3.2% 였으면 “예상보다 낮네” 하고 주가가 오르거든요. 이 예상 대비 실제(surprise)를 계산하고, 여러 지표를 묶어 나만의 경제 서프라이즈 지수 까지 만들어 봅니다.
💡 이 글에서 다루는 것
matplotlib한글 폰트 깨짐 먼저 잡기 (macOS/Windows)- CPI 전년 대비 + 연준 목표선 + 침체 음영(NBER
USREC) 깔끔하게 그리기- 여러 지표를 다중 패널(subplots) 한 화면에 모으기
- 단위가 다른 지표를 정규화해 한 그래프에서 겹쳐 보기
- 발표 서프라이즈 = 실제 − 예상, 예상치는 어디서 구하나
- 지표별 표준화(z-score) 후 합쳐서 나만의 경제 서프라이즈 지수 만들기
0. 들어가기 전에 — 한글 폰트부터
matplotlib 으로 한글 제목·라벨을 넣으면 처음엔 네모(□□□)로 깨져요. 이거 안 잡고 시작하면 그래프마다 다시 그리게 되니, 맨 앞에서 한 번 잡고 가요.
import matplotlib.pyplot as plt
# macOS
plt.rcParams["font.family"] = "AppleGothic"
# Windows 면 "Malgun Gothic", 리눅스(나눔폰트 설치 시) 면 "NanumGothic"
# 음수 부호(-)도 같이 깨지니 이 줄도 필수
plt.rcParams["axes.unicode_minus"] = False
⚠️ 폰트를 바꿨는데도 계속 깨지면,
matplotlib의 폰트 캐시가 옛 정보를 들고 있는 경우가 많아요.import matplotlib; matplotlib.get_cachedir()로 캐시 폴더 찾아 지우고 파이썬을 다시 띄우면 풀려요.
1. 단일 지표 깔끔하게 그리기
1편에서 받았던 CPI 전년 대비 상승률을 그려볼게요. 그냥 선만 긋는 게 아니라 연준 목표선(2%) 을 같이 깔면 “지금 목표보다 얼마나 위인가” 가 바로 읽혀요.
import pandas_datareader.data as web
import matplotlib.pyplot as plt
cpi = web.DataReader("CPIAUCSL", "fred", "2010-01-01")["CPIAUCSL"]
cpi_yoy = cpi.pct_change(12) * 100 # 전년 대비 상승률(%)
fig, ax = plt.subplots(figsize=(11, 4))
ax.plot(cpi_yoy.index, cpi_yoy, label="CPI 전년 대비(%)")
ax.axhline(2, color="gray", ls="--", lw=1, label="연준 목표 2%")
ax.set_title("미국 소비자물가 상승률")
ax.legend()
plt.tight_layout()
axhline(2, ...) 한 줄이 수평 기준선이에요. 목표선을 깔아두면 코로나 이후 9%까지 치솟았다가 다시 내려오는 그림이 훨씬 직관적으로 보여요.
2. 침체 음영 입히기 — 프로처럼 보이는 한 끗
경제 그래프에서 회색 세로 띠로 칠해진 침체기 음영 을 본 적 있을 거예요. 이게 있으면 “이 지표가 침체 직전에 어떻게 움직였나” 가 한눈에 들어와요. FRED 에 침체 여부를 1/0 으로 알려주는 USREC(NBER 기준) 시리즈가 있어서, 이걸 음영으로 깔면 돼요.
import pandas_datareader.data as web
import matplotlib.pyplot as plt
cpi_yoy = web.DataReader("CPIAUCSL", "fred", "2000-01-01")["CPIAUCSL"].pct_change(12) * 100
rec = web.DataReader("USREC", "fred", "2000-01-01")["USREC"] # 침체기=1, 평시=0
fig, ax = plt.subplots(figsize=(11, 4))
ax.plot(cpi_yoy.index, cpi_yoy, label="CPI 전년 대비(%)")
ax.axhline(2, color="gray", ls="--", lw=1)
# USREC 가 1인 구간을 세로 띠로 음영 처리
ax.fill_between(rec.index, 0, 1, where=rec.values == 1,
transform=ax.get_xaxis_transform(),
color="gray", alpha=0.2, label="침체기(NBER)")
ax.set_title("CPI 상승률과 침체기")
ax.legend()
plt.tight_layout()
핵심은 transform=ax.get_xaxis_transform() 이에요. 이걸 주면 fill_between 의 y 값 0~1 이 데이터 좌표가 아니라 그래프 높이의 0%~100% 로 해석돼서, 지표 값이 얼마든 음영이 위아래를 꽉 채워요.
3. 여러 지표를 한 화면에 — 다중 패널
지표 하나씩 보는 것도 좋지만, 인플레이션·고용·금리를 위아래로 쌓아 같은 시간축에서 보면 관계가 보여요. “실업률이 바닥일 때 금리를 올렸구나” 같은 게요. subplots 로 패널을 나눕니다.
import pandas_datareader.data as web
import matplotlib.pyplot as plt
panels = [
("CPIAUCSL", "CPI 전년 대비(%)", True), # True = YoY 변환 필요
("UNRATE", "실업률(%)", False),
("FEDFUNDS", "기준금리(%)", False),
]
fig, axes = plt.subplots(len(panels), 1, figsize=(11, 8), sharex=True)
for ax, (code, label, is_yoy) in zip(axes, panels):
s = web.DataReader(code, "fred", "2005-01-01")[code]
if is_yoy:
s = s.pct_change(12) * 100
ax.plot(s.index, s)
ax.set_title(label, loc="left", fontsize=11)
fig.suptitle("거시 지표 한눈에", fontsize=14)
plt.tight_layout()
sharex=True 로 x축(날짜)을 공유해서, 세 패널의 시점이 정확히 위아래로 맞아요. is_yoy 플래그로 CPI 만 전년 대비로 변환하는 게 포인트예요.
4. 단위가 다른 지표 겹쳐 보기 — 정규화
1편 마지막에서 살짝 예고했던 부분이에요. 실업률(4%)과 CPI 지수(321)처럼 단위·스케일이 다른 지표를 한 그래프에 그냥 그리면, 큰 숫자가 작은 숫자를 납작하게 눌러버려요. 그래서 시작점을 100으로 맞추는 정규화 를 거칩니다.
import pandas_datareader.data as web
import matplotlib.pyplot as plt
codes = {"CPIAUCSL": "CPI", "UNRATE": "실업률", "DGS10": "국채10년", "VIXCLS": "VIX"}
df = web.DataReader(list(codes), "fred", "2021-01-01").rename(columns=codes).ffill().dropna()
normalized = df / df.iloc[0] * 100 # 첫 행을 100으로 맞춤
normalized.plot(figsize=(11, 5), title="거시 지표 추세 (시작=100)")
plt.tight_layout()
이러면 절대값이 아니라 상대 변화 로 비교돼서, “금리는 두 배가 됐는데 CPI 는 완만하게 올랐네” 같은 비교가 한 화면에서 됩니다. 정규화는 결국 “각자 자기 출발선 대비 몇 배가 됐나” 로 환산하는 거예요.
5. 발표 서프라이즈 — 시장이 진짜 보는 것
여기서부터가 이번 편의 진짜 핵심이에요. 시장은 지표의 값 보다 예상 대비 차이 에 반응해요. 같은 CPI 3% 라도,
- 예상 3.2% → 실제 3.0% : “예상보다 낮네!” → 금리 인하 기대 → 주가 ↑
- 예상 2.8% → 실제 3.0% : “예상보다 높네…” → 인하 지연 우려 → 주가 ↓
이 예상 대비 실제의 차이 를 발표 서프라이즈(surprise)라고 불러요. 공식은 단순해요.
💡 서프라이즈 = 실제(actual) − 예상(forecast)
5-1. 예상치(consensus)는 어디서 구하나
여기서 한 가지 솔직하게 짚을 게 있어요. 실제값(actual)은 FRED 에 다 있지만, 예상치(consensus forecast)는 FRED 에 없어요. 예상치는 여러 이코노미스트 전망의 중앙값이라, 보통 경제 캘린더 쪽에서 가져와요.
| 예상치 출처 | 비고 |
|---|---|
| Investing.com 경제 캘린더 | 화면에서 forecast/actual 같이 보임, 수기 복사 |
| Trading Economics | API 제공(일부 유료) |
| 직접 CSV 관리 | 발표 때마다 예상/실제 한 줄씩 적립 |
⚠️ 한때 많이 쓰던
investpy패키지는 Investing.com 의 차단으로 현재 정상 동작하지 않아요. 자동화가 꼭 필요하면 Trading Economics 같은 정식 API 를 쓰고, 가볍게 시작할 거면 발표 때마다 예상/실제를 표로 적립하는 방식 을 추천드려요.
5-2. 서프라이즈 계산하기
경제 캘린더에서 옮겨온 발표 기록이 이렇게 있다고 해볼게요. 실제값은 1편에서 FRED 로 받은 그 숫자들이에요.
import pandas as pd
cal = pd.DataFrame({
"date": pd.to_datetime(["2026-03-12", "2026-04-10", "2026-05-13"]),
"forecast": [3.1, 3.0, 2.9], # 발표 직전 컨센서스
"actual": [3.05, 2.94, 2.88], # FRED 로 받은 실제 CPI YoY
})
cal["surprise"] = cal["actual"] - cal["forecast"]
print(cal)
date forecast actual surprise
0 2026-03-12 3.1 3.05 -0.05
1 2026-04-10 3.0 2.94 -0.06
2 2026-05-13 2.9 2.88 -0.02
surprise 가 음수라는 건 실제가 예상보다 낮았다 는 뜻이에요. 인플레이션에서 음수 서프라이즈는 보통 시장이 반기는 방향(물가가 예상보다 덜 올랐다)이죠.
6. 여러 지표를 하나의 서프라이즈 지수로
서프라이즈를 지표 하나만 보면 반쪽이에요. CPI·고용·소매판매가 다 같이 예상을 웃돌면 “경제가 예상보다 뜨겁다” 는 큰 그림이 나오거든요. 월가에서 보는 씨티 경제 서프라이즈 지수(Citi Economic Surprise Index) 가 바로 이 아이디어예요. 직접 축소판을 만들어 봅니다.
문제는 단위가 제각각이라는 거예요. CPI 서프라이즈는 0.05(%p), 고용은 22(천 명) 단위라 그냥 더하면 고용이 다 먹어버려요. 그래서 지표별로 표준화(z-score) 해서 단위를 없앤 뒤 합쳐요.
import pandas as pd
# 이번 주 발표들 — 예상/실제 + 그 지표의 과거 서프라이즈 표준편차
cal = pd.DataFrame({
"indicator": ["CPI YoY", "비농업고용", "소매판매 MoM", "Core PCE YoY"],
"forecast": [2.9, 180.0, 0.4, 2.8],
"actual": [2.88, 158.0, 0.2, 2.85],
"surprise_std": [0.15, 55.0, 0.30, 0.12], # 과거 서프라이즈 표준편차
})
cal["surprise"] = cal["actual"] - cal["forecast"]
cal["z"] = cal["surprise"] / cal["surprise_std"] # 표준화 → 단위 제거
print(cal[["indicator", "surprise", "z"]].round(2))
indicator surprise z
0 CPI YoY -0.02 -0.13
1 비농업고용 -22.00 -0.40
2 소매판매 MoM -0.20 -0.67
3 Core PCE YoY 0.05 0.42
z 는 “그 지표 기준으로 평소보다 몇 표준편차나 벗어났나” 예요. 이러면 CPI 0.02 와 고용 22 처럼 단위가 다른 서프라이즈를 같은 잣대로 비교할 수 있어요.
6-1. 부호를 맞춰서 합치기
마지막 한 끗이 있어요. “예상보다 높은 게 좋은 건지” 가 지표마다 달라요. 고용·소매판매는 높을수록 경제가 강한 거지만, 인플레이션은 높을수록 부담이에요. “성장 모멘텀” 관점의 지수를 만들려면 인플레 쪽 부호를 뒤집어야 해요.
# 높을수록 '강한 성장'이면 +1, 인플레처럼 높을수록 부담이면 -1
cal["direction"] = [-1, +1, +1, -1] # CPI, 고용, 소매판매, Core PCE
cal["z_dir"] = cal["z"] * cal["direction"]
surprise_index = cal["z_dir"].mean()
print(f"이번 주 성장 모멘텀 서프라이즈 지수: {surprise_index:.2f}")
이번 주 성장 모멘텀 서프라이즈 지수: -0.34
지수가 음수면 “이번 주 발표들은 대체로 예상보다 약했다(경제가 예상보다 식었다)” 는 뜻이에요. 이 한 숫자를 매주 적립해서 시계열로 그리면, 그게 바로 직접 만든 미니 경제 서프라이즈 지수예요. surprise_std 는 그 지표의 과거 서프라이즈를 충분히 모아 df["surprise"].std() 로 구해두면 점점 정교해져요.
마무리
1편에서 지표를 받는 법을, 2편에서 그걸 보는(대시보드) 법과 해석하는(서프라이즈) 법을 정리했어요. 정리하면 이런 흐름이에요.
matplotlib한글 폰트 먼저 잡고 시작- 단일 지표 + 목표선 + 침체 음영(
USREC)으로 추세 읽기 - 다중 패널·정규화로 지표 간 관계 보기
- 서프라이즈 = 실제 − 예상, 예상치는 경제 캘린더에서
- 지표별 표준화(z) → 부호 맞춰 합산 → 나만의 서프라이즈 지수
여기까지 따라오면 “지표를 받아서 → 그리고 → 예상 대비로 해석” 하는 한 사이클이 손에 잡혀요. 다음은 이 서프라이즈 지수가 실제 주가·금리와 얼마나 같이 움직이는지 상관을 따져보는 건데, 그건 또 백테스트의 영역이라 퀀트 백테스트 글 쪽과 이어집니다.
일단 오늘은 여기까지…..
지표를 숫자로만 보던 데서 한 발 나아가, 직접 그리고 해석하는 재미를 느껴보셨으면 좋겠어요.