(시험 준비하는사이 세상이 또 바꼈다...)
(원하는 내용을 주저리주저리 작성하니 이제는 코드를 고칠 필요도 없이 원하는 코드를 작성해주고...)
딥러닝으로 찾은 원화 변동성
문제 설정: 원인을 어떻게 정의할 것인가
"원화가 변동성이 크다"는 서술을 데이터로 증명하려면 두 가지 덫을 피해야 한다.
첫째, 단일 통화만 보면 안 된다. VIX가 올랐을 때 원화 변동성이 커졌다는 사실만으로 "VIX가 원인이다"라고 말할 수 없다. 같은 시점 엔화·유로화도 똑같이 반응했다면 그건 "원화 고유의 원인"이 아니라 전 세계 공통 충격이기 때문이다.
둘째, 단변량 예측만으로는 부족하다. 환율 자체를 예측하는 건 거의 불가능에 가깝고, 우리가 알고 싶은 건 "어떤 외부 요인이 원화 변동성을 얼마나 설명하는가"이다. 그래서 타겟을 환율 수준이 아닌 20일 실현변동성(rolling std × √252)으로 잡았다.
이 두 덫을 함께 피하기 위해 분석 구조를 이렇게 짰다:
- 거시·시장 피처를 5개 범주로 묶는다 (rates, global_risk, capital_flow, commodities, peer_fx).
- 동일한 피처셋으로 KRW, JPY, EUR, TWD 네 통화의 실현변동성을 각각 예측하는 딥러닝 모델을 학습한다.
- 각 범주가 각 통화 예측에 얼마나 기여하는지 측정한다.
- "KRW-specific score" = KRW 기여도 − 다른 세 통화 평균 기여도로 정의한다. 이 값이 높을수록 "다른 통화에는 별로 안 먹히는데 원화에는 유독 먹히는" 범주, 즉 원화 고유 민감도의 증거가 된다.
그리고 "기여도"를 측정하는 방법을 두 가지로 두어 서로 교차 검증했다. 한쪽에서만 나오는 결과는 믿지 않고, 두 방법이 동시에 가리키는 것만 믿기로 한 것이다.
데이터: 무료로 찾을 수 있는 최대한 다양한 소스
신뢰할 수 있는 분석을 위해 세 개 오픈 데이터 소스에서 20개 넘는 시계열을 끌어왔다.
- yfinance — 원/달러·엔/달러·유로/달러·대만/달러 환율, KOSPI, S&P 500, 필라델피아 반도체(SOX), VIX, MOVE, WTI, 금, 구리, 달러인덱스 등
- FRED (세인트루이스 연은) — 미 국채 10년·2년 금리, 연방기금금리, 10년 기대인플레(breakeven), 광의 달러지수 등
- pykrx — KOSPI 외국인 순매수 (자본 유출입의 직접 프록시)
이 원시 시리즈에서 로그수익률, 20일 실현변동성, 한-미 금리 기울기(US10Y − US2Y), VIX 변화량, 외국인 순매수 60일 z-score 등 파생 피처를 만들었다. 그리고 이들을 경제적 의미에 따라 다섯 범주로 묶었다:
범주 의미 대표 피처
| rates | 금리·기대인플레 | US10Y, US2Y, Fed funds, breakeven, 금리 기울기 |
| global_risk | 글로벌 위험지표 | VIX, MOVE, 달러인덱스 |
| capital_flow | 자본 유출입 프록시 | KOSPI, SOX, SPX 수익률, 외국인 순매수 z-score |
| commodities | 원자재 | WTI, 금, 구리 수익률 |
| peer_fx | 동조 통화 움직임 | JPY, EUR, CNY 수익률 |
개별 피처 단위로 중요도를 뽑으면 상관 강한 변수들 사이에 기여도가 분산되어 해석이 모호해진다. 범주 단위로 통째로 취급하면 "금리가 중요" 같은 명쾌한 답을 얻을 수 있다.
두 가지 접근
Approach A — Rolling Permutation Importance
아이디어: 모델을 정상적으로 학습시킨 뒤, 특정 범주의 피처들을 임의로 섞어서(shuffle) 예측 오차가 얼마나 증가하는지 본다. 오차가 많이 늘면 그 범주가 원래 예측에 크게 기여했다는 뜻이다.
모델은 외생변수를 지원하는 세 개를 앙상블했다:
- NHITS — 다중 해상도로 시계열을 분해하는 MLP 기반 모델
- TSMixerx — IBM의 MLP-Mixer 아키텍처, 외생변수 지원 버전
- TFT (Temporal Fusion Transformer) — 해석 가능성을 목표로 설계된 Transformer
여기서 큰 함정이 하나 있었다. 초기 버전에서 nf.predict(futr_df=shuffled)로 호출했는데, neuralforecast의 futr_df는 "미래 외생변수"만 받는 인자다. hist_exog_list로 지정한 과거 피처는 predict 시점엔 무시되고 학습 시 저장된 과거가 사용된다. 그래서 어떤 컬럼을 섞어도 예측은 똑같이 나왔고 모든 ΔMAE가 0으로 찍혔다. 삽질 끝에 발견한 이 버그를 해결하는 핵심 함수가 rolling_predict다:
def rolling_predict(nf, full_df, cutoff_idxs):
"""훈련된 nf 로 각 cutoff 지점에서 h-step 예측을 모음.
핵심: predict(df=hist) 로 호출해야 shuffle된 과거가 실제로 반영됨."""
preds = []
for i in cutoff_idxs:
hist = full_df.iloc[:i].copy()
if len(hist) < INPUT_SIZE + HORIZON + 5:
continue
p = nf.predict(df=hist).reset_index()
truth = full_df.iloc[i:i+HORIZON][["ds", "y"]].reset_index(drop=True)
p["y_true"] = truth["y"].values[:len(p)]
preds.append(p)
return pd.concat(preds, ignore_index=True)
df=hist로 넘기면 neuralforecast는 해당 history를 실제로 참조하기 때문에, shuffle된 피처가 제대로 예측에 반영된다. 60개 cutoff 지점에서 5영업일 앞을 예측하고, 전체 구간에 대해 앙상블 MAE를 기준값으로 저장한다. 그 다음 범주별로:
for cat, cols in CATEGORIES.items():
diffs = []
for r in range(N_PERMUTE):
shuffled = long_df.copy()
for c in cols:
shuffled[c] = rng.permutation(shuffled[c].values)
p = rolling_predict(nf, shuffled, cutoffs)
diffs.append(ensemble_mae(p) - base_mae)
scores[cat] = float(np.mean(diffs))
범주에 속한 피처를 동시에 섞는 게 포인트다. 상관 높은 피처들을 따로 섞으면 기여도가 분산되니까, 범주 전체를 한꺼번에 무너뜨려 "이 범주를 통째로 없애면 얼마나 손해인가"를 측정한다.
Approach B — Feature-Gate Autoencoder
아이디어는 독자의 직관에서 시작했다: 딥러닝 내부의 가중치 행렬 자체가 피처 중요도를 담고 있지 않을까? 특히 오토인코더의 latent space는 입력의 정보를 압축하는데, 그 압축 과정에서 어떤 피처에 얼마나 "열려" 있는지를 보면 중요도가 나올 것이다.
이 직관을 가장 깔끔하게 실현하는 방법이 learnable feature gate다. 입력 앞단에 학습 가능한 벡터 w ∈ ℝ^F를 두고 sigmoid(w)를 피처별 스칼라로 곱한다. 학습이 끝나면 이 값이 "모델이 최종적으로 열어두기로 결정한 정보의 통로 너비"가 된다.
class FeatureGateAE(nn.Module):
def __init__(self, n_feat, seq_len=INPUT_SIZE, hidden=128, latent=32):
super().__init__()
# 게이트는 0에서 시작 → sigmoid(0)=0.5, 모두 절반 열린 상태
self.gate_logits = nn.Parameter(torch.zeros(n_feat))
self.enc = nn.Sequential(
nn.Linear(seq_len * n_feat, hidden*2), nn.GELU(), nn.Dropout(0.2),
nn.Linear(hidden*2, hidden), nn.GELU(), nn.Dropout(0.2),
nn.Linear(hidden, latent),
)
self.recon = nn.Sequential(
nn.Linear(latent, hidden), nn.GELU(),
nn.Linear(hidden, seq_len*n_feat),
)
self.head = nn.Sequential(
nn.Linear(latent, hidden//2), nn.GELU(), nn.Dropout(0.1),
nn.Linear(hidden//2, 1),
)
def forward(self, x): # x: [B, T, F]
g = torch.sigmoid(self.gate_logits) # [F]
xg = x * g.view(1, 1, -1)
z = self.enc(xg.reshape(x.size(0), -1))
x_hat = self.recon(z).view_as(x)
y_hat = self.head(z).squeeze(-1)
return y_hat, x_hat, g
손실 함수는 세 개의 합이다:
loss = pred_loss + 0.3 * recon_loss + L1_LAMBDA * l1_gate
- pred_loss — 타겟(다음 시점 실현변동성) 예측 오차
- recon_loss — 입력 시퀀스 재구성 오차. 이게 없으면 게이트가 모든 피처를 닫아버리는 퇴화 해(collapse)가 나온다. 재구성을 강제하면 "실제 정보를 담은 피처는 열려 있어야 한다"는 압력이 생긴다.
- L1 페널티 — ||sigmoid(w)||₁에 작은 가중치를 곱한 항. 쓸데없는 피처의 게이트를 0으로 밀어내 스파스성을 유도한다.
학습 끝나면 model.importance()가 sigmoid(w) 값을 [0, 1] 범위로 뱉어낸다. 이게 직접적인 중요도다.
여기에 보조 지표 하나를 더 뽑았다. Gradient × Input saliency:
model.eval()
Xva_g = Xva_t.clone().requires_grad_(True)
yv, _, _ = model(Xva_g)
yv.sum().backward()
sal = Xva_g.grad.detach().abs().mean(dim=(0,1)).cpu().numpy()
게이트가 "정적 중요도"라면 saliency는 "동적 민감도"다. 게이트는 학습이 끝난 뒤 모델이 "이 피처를 얼마나 열어두기로 했는가"를 보여주고, saliency는 "검증 데이터에서 이 피처가 실제로 예측값을 얼마나 움직였는가"를 보여준다. 두 지표가 같은 범주를 가리키면 강한 증거다.
Colab L4 GPU 활용
세 개 모델 × 네 개 통화 × rolling 평가 = 계산량이 만만치 않다. L4 GPU를 제대로 쓰기 위해 Trainer에 명시적으로 GPU 옵션을 넘겼다:
TRAINER_KW = dict(
accelerator="gpu", devices=1,
precision="16-mixed", # FP16 → 2~3배 가속
enable_progress_bar=False, enable_model_summary=False, logger=False,
)
FP16 혼합정밀도로 학습하면 L4의 Tensor Core를 활용해 체감 속도가 2~3배 빨라진다. 또 batch_size=64, windows_batch_size=512로 배치를 키워 22GB VRAM을 활용했다. 전체 파이프라인이 약 15~20분에 끝났다.
결과: 네 통화의 시선이 만드는 지도
세 가지 방법으로 뽑은 KRW-specific 점수를 나란히 놓으면 이렇다:
범주 Approach A (permutation) Approach B-gate Approach B-saliency
| peer_fx | −0.0003 (1위) | 0.0302 (2위) | 0.0215 (1위) |
| commodities | −0.0003 (2위) | 0.0341 (1위) | 0.0114 (2위) |
| capital_flow | −0.0004 | 0.0171 | 0.0098 |
| rates | −0.0008 | 0.0168 | −0.0110 |
| global_risk | −0.0014 | 0.0197 | −0.0139 |
세 방법 모두에서 최상위 두 자리는 peer_fx와 commodities가 차지한다. 나머지 세 범주는 들쭉날쭉하거나 아래쪽에 깔린다. 이게 이 분석의 핵심 발견이다.
발견 1: 원화는 "지역 통화 블록"의 일부로 움직인다
peer_fx 범주는 엔·유로·위안화의 수익률로 구성된다. 이것이 세 방법에서 모두 상위 두 자리에 들었다는 것은, 원화 변동성이 다른 주요 통화의 움직임에 유독 민감하게 반응한다는 뜻이다. 특히 saliency 관점에서는 단연 1위였는데, 이는 검증 기간 동안 엔·위안이 흔들릴 때마다 원화 예측값도 같이 흔들렸다는 것이다.
이는 외환시장의 오래된 관찰과 부합한다. 원화는 구조적으로 엔화·위안화와 동조하는 경향이 강하고, 아시아 통화 블록 의 한 축으로 취급받는다. 한·일이 동시에 해외 투자를 확대하는 구조적 공통점, 수출 경쟁 관계, 위안화를 통한 간접 노출 등이 복합적으로 작용한다.
발견 2: 원자재는 원화의 아킬레스건
commodities는 WTI·금·구리 수익률로 구성된다. Approach B-gate에서 1위, 다른 두 방법에서 2위로 매우 일관된 신호를 보였다.
경제학적으로도 말이 된다. 한국은 에너지의 95% 이상을 수입에 의존하는 극단적 에너지 수입국이다. 유가가 급등하면 무역수지가 직격탄을 맞고, 이는 외환 수급을 통해 원화에 즉각 반영된다. 반면 일본은 에너지 수입 의존도가 비슷해도 엔캐리트레이드라는 별개 동학이 지배적이고, 유로는 원자재 수입보다 금리 차가 더 결정적이다. 그래서 원자재 민감도에서 원화만 튀는 것이다.
게이트 관점에서는 원자재가 1위라는 건 **모델이 원화 변동성을 학습할 때 다른 어떤 범주보다 원자재 채널을 "활짝 열어두었다"**는 의미다. 모델이 그렇게 배운 데는 이유가 있다.
발견 3: 금리와 글로벌 위험은 의외로 "원화 고유"가 아니다
가장 흥미로운 결과는 rates와 global_risk의 음수 KRW-specific 점수다. 특히 Approach A에서 global_risk는 −0.0014로 가장 낮았다.
이걸 뒤집어 읽으면 이런 얘기가 된다: "VIX·달러인덱스·MOVE 같은 글로벌 위험지표는 원화뿐 아니라 엔·유로·대만달러의 변동성에도 강력하게 영향을 준다. 오히려 피어 통화에 더 먹힌다. 원화만 유독 튀는 요인은 아니다."
직관과 다를 수 있다. "VIX 오르면 원화 약세"는 수도 없이 들었던 얘기 아닌가? 맞다. 하지만 그건 모든 이머징 통화에 동일하게 적용된다. VIX는 원화 변동성의 "배경 조명"이지, 원화만 골라 때리는 스포트라이트가 아니다. 이 분석은 "원화만의 민감도"를 측정했기 때문에, 공통 요인은 상대적으로 점수가 낮게 찍힌 것이다.
발견 4: 원화는 거시 피처로 예측하기가 원래 어렵다
Approach A의 모든 KRW-specific 점수가 음수라는 점은 따로 주목할 만하다. 피어 통화는 섞으면 MAE가 눈에 띄게 올라간다(rates 범주에서 JPY +0.0005, EUR +0.0009, TWD +0.0008). 반면 KRW는 같은 범주에서 오히려 소폭 감소(−0.00009). 즉 KRW 모델은 이들 피처를 피어 통화만큼 활용하지 못했다.
이건 두 가지로 해석할 수 있다. 하나는 "우리가 쓴 피처셋이 KRW를 충분히 설명하지 못한다"는 것이다. 즉 환율 결정 요인 중 이 모델에 없는 것(정책 개입, 레버리지, 뉴스 충격, 국민연금 헤지 스케줄 등)이 원화에서 상대적으로 큰 비중을 차지한다는 뜻이다. 실제로 2025~2026년 원화 고환율 국면을 두고 일부 전문가들이 "과거 경험으로 가늠하기 어려운 국면"이라고 한 표현과 맞닿는다.
다른 하나는 "원화는 구조적으로 더 노이지하다"는 해석이다. 같은 피처로 피어 통화는 더 잘 맞추는데 원화만 못 맞춘다면, 원화엔 설명 안 되는 고유 변동이 남아 있다는 것이다. 이 "설명 안 됨" 자체가 원화가 유별나게 움직인다는 경험적 서술의 통계적 근거일 수 있다.
최종 순위: 투표로 결정하기
세 방법의 상위 2위 안에 든 범주에 가중치를 주어 합산하면(1위 2점, 2위 1점):
- commodities: 1 + 2 + 1 = 4점 또는 5점 (반올림 타이 처리 방식에 따라)
- peer_fx: 2 + 1 + 2 = 5점 또는 4점
스크립트는 타이 처리 결과 commodities를 최종 승자로 뽑았지만, 점수 차이는 한 끗이고 실질적으로 두 범주가 공동 1위다. 한쪽만 골라야 한다면 이렇게 정리하는 게 정직하다:
원화 변동성의 가장 큰 고유 원인은 두 가지다. ① 아시아 지역 통화(엔·위안)의 움직임에 대한 동조성, ② 원자재 가격에 대한 수입국 민감도.
반면 금리 격차와 VIX 같은 글로벌 위험은 누구나 아는 원화 약세 요인이지만, 이들은 원화만의 요인이 아니라 모든 피어 통화에 동일하게 작용하는 공통 배경이다. 원화를 특별하게 만드는 건 이들이 아니다.
결과를 두고 조심해야 할 점들
이 분석에는 분명한 한계가 있다. 블로그에 쓰면서 감추는 건 비겁하니까 정직하게 적어둔다.
1. Permutation 점수의 절댓값이 매우 작다.
Approach A의 ΔMAE가 모두 10⁻⁴ 규모다. 이는 모델이 이 피처들로 원화 변동성을 그다지 크게 바꾸지 못했다는 뜻이기도 하다. 순위는 의미가 있지만 "rates가 0.001 중요하다" 같은 절대 수치 해석은 조심해야 한다.
2. 검증 기간 의존성.
60개 rolling cutoff가 검증 구간에 걸쳐 있는데, 이 구간이 어떤 레짐(예: 2024 말~2025 상반기)인지에 따라 결과가 달라질 수 있다. 더 강건한 결론을 위해선 여러 시점에서 다시 돌려보고 일관성을 봐야 한다.
3. 피처셋의 불완전성.
이 분석에는 정책 개입(외환 당국의 미세조정 매매), 레버리지 포지션, 국민연금 헤지 물량, 뉴스 텍스트가 들어 있지 않다. 원화를 흔든다고 지목되는 "서학개미 환전", "대미 투자 약속 200억 달러" 같은 요인은 직접 관측 변수가 없어 모델에 반영되지 않았다. KRW-specific 점수가 전반적으로 작은 건 이 결측 변수 탓일 가능성이 크다.
4. 상관과 인과의 구분.
모든 중요도 지표는 예측 기여도이지 인과 관계가 아니다. "peer_fx가 원화를 움직인다"가 아니라 "peer_fx 움직임이 원화 움직임과 함께 간다"일 수도 있다. 진짜 인과를 보려면 VAR 모델, 그레인저 인과, 자연실험 등 다른 도구가 필요하다.
5. 두 방법의 불일치 구간.
Approach A는 rates/global_risk를 명확히 "원화 고유 아님"으로 본 반면, Approach B-gate는 이들에게도 양수 점수를 줬다. 이는 두 방법이 측정하는 게 엄밀히 다르기 때문이다. Permutation은 "이걸 없애면 예측이 얼마나 망가지나"를, gate는 "모델이 이걸 얼마나 활용하기로 했나"를 본다. 같은 질문이 아니다. 그래서 세 방법 모두에 걸친 일관성을 본 것이고, 그게 peer_fx와 commodities였던 것이다.
정리
원화 환율의 변동성이 왜 유별난지, 딥러닝 모델 세 개와 해석 기법 두 종류로 답을 찾아봤다. 그 결과는 다음 한 문장으로 요약된다.
원화는 글로벌 공통 충격 위에, 아시아 지역 통화 블록과의 동조성 그리고 원자재 수입국 민감도가 얹혀 있는 통화다. 그래서 같은 VIX 충격에도 원화가 더 흔들리는 게 아니라, VIX 충격 때 원자재도 함께 움직이고 엔·위안도 함께 움직이면서 원화는 이중·삼중으로 얻어맞는 구조다.
금리 격차, 글로벌 위험지표 같은 단골 레퍼토리는 원화 변동성의 "배경"이지 "범인"이 아니다. 범인은 원화를 다른 통화보다 더 세게 때리는 특유의 경로 — 지역 통화 동조성과 원자재 노출이다.
분석을 시작할 땐 "M2 팽창", "한미 금리차", "서학개미"같은 표준 설명 중 하나가 1위로 뽑히지 않을까 기대했는데, 데이터는 다른 얘기를 해줬다. 이게 데이터 분석의 재미이기도 하다.
neuralforecast·yfinance·pykrx·pandas_datareader만 있으면 Colab L4 인스턴스에서 15~20분 안에 재현된다. 관심 있으시면 피처셋에 뉴스 텍스트 임베딩이나 국민연금 해외투자 시계열을 추가해 재실행해 보시길 권한다 — 결과가 어떻게 바뀌는지 함께 궁금하다.
-----------------------여기까지가 클로드만을 활용한 결과물-----------------------
실제 데이터 그래프는 아래와 같이 나왔다

한국 원화는 사실 평균적인 사람들의 예상 혹은 데이터와는 관련이 없고 그때 그때 상황과 관련있는게 아닐까
- 타 통화들과는 다르게 움직이는 다르게 민감도가 모두 음수로 나타남
- 타 통화들은 생각한 피처들을 활용했지만 원화만큼은 제대로 활용하지 못하였거나 다르게 활용
원화 변화에 가장 크게 작용하는 요인은 원자재와 제일 관련이 있었고, 수출입에 큰 영향을 미치는것으로 보임
우리가 생각하는 서학개미들의 요인은 capital_flow 로 정의되는데 다른 요인보다 영향을 적게 미치는것으로 보임
또한 미국금리(rates)와 관련성도 생각보다 작아보임
결국은 지금 당장 상황이 다른 통화에 비해 민감하게 반응하는것을 데이터로 볼 수 있다.
'데이터 분석 > 금융' 카테고리의 다른 글
| 원화 환율은 왜...? - 3 (+대한항공) (0) | 2026.04.13 |
|---|---|
| 원화 환율은 왜...? - 2 (0) | 2026.04.09 |
| 코인 옵션 데이터 활용 0-2 (0) | 2025.09.01 |
| 코인 옵션 데이터 활용 0-1 (0) | 2025.08.30 |
| 코인 옵션 데이터 활용 0. (0) | 2025.08.26 |