feat: 3-variable macro model (USDKRW+RETAIL_SALES+INVEST_RATE), forced_vars support, methodology sync

This commit is contained in:
Variet Agent
2026-03-11 17:30:06 +09:00
parent f35ab389d5
commit 87725b7c19
4 changed files with 47 additions and 24 deletions

View File

@@ -26,6 +26,8 @@ model:
rho: 0.20
# 신용등급 체계 (한국 3사 공통)
rating_grades: ["AAA", "AA", "A", "BBB", "BB", "B", "D"] # 7x7 (CCC제외, Zt추정용)
# 거시 회귀모형 강제 변수 (null이면 stepwise AIC 자동선택)
macro_vars: ["USDKRW", "RETAIL_SALES", "INVEST_RATE"]
# 시나리오 설정
scenarios:

View File

@@ -287,32 +287,40 @@ Zt는 "신용사이클"이라는 추상적 개념입니다. 이를 관측 가능
2. **예측 가능성**: 거시 전망치(IMF WEO, KDI 등)를 입력하면 미래 Zt를 예측할 수 있음
3. **시나리오 분석**: "만약 GDP가 -2%이고 실업률이 5%이면?"이라는 질문에 답할 수 있음
**모형 구조:**
**변수 풀 (37개 ECOS 변수):**
BOK ECOS 100대 통계지표 및 주요 거시경제변수 37개를 후보 풀로 구성:
- 성장(GDP성장률), 고용(실업률, 고용률), 금리(기준금리, CD, 국고채3Y/10Y, 회사채AA/BBB)
- 물가(CPI, 수입물가, 생산자물가), 경기지수(선행/동행), 심리(CSI, BSI)
- 생산(광공업, 서비스업), 교역(수출/수입, 수출입대GNI비율)
- 환율(원/달러), 통화(M2), 부도(어음부도율/금액), 주식(KOSPI)
- 부동산(주택매매가격), 가계(가계부채)
- 투자(설비투자, 건설투자증감률, 총고정자본형성, 총저축률, 국내총투자율)
- 제조업(평균가동률)
**모형 구조 (3변수 강제 지정):**
37개 변수에서 3변수 조합 7,770개를 전수 탐색(exhaustive search)하여,
**부호 일관성**을 만족하는 최적 조합을 선택:
```
Z_t = β₀ + β₁·GDP_growth_t + β₂·Unemployment_t + β₃·Base_Rate_t
+ β₄·CD_Rate_t + β₅·CPI_growth_t + β₆·Leading_Index_t + ε_t
Z_t = β₀ + β₁·USDKRW_t + β₂·RETAIL_SALES_t + β₃·INVEST_RATE_t + ε_t
```
**변수 선택 (Forward Stepwise, AIC 기준):**
| 변수 | 구분 | 계수 부호 | 경제적 근거 |
|------|------|:---------:|------------|
| USDKRW | 환율 (원/달러) | | 원화 약세 → 외국인 자본유출, 수입원가 상승 → 기업 부담↑ → Zt↓ |
| RETAIL_SALES | 소매판매액지수 | + | 내수 소비 활성화 → 기업 매출·수익성↑ → Zt↑ |
| INVEST_RATE | 국내총투자율 (%) | + | 투자 확대 → 경기 확장 → 부도 감소 → Zt↑ |
모든 6개 변수를 한꺼번에 넣으면 과적합(overfitting) 위험이 있습니다 (26개 관측치 대비 7개 파라미터).
- **R² = 0.43** (비표준화), **7/8 검증 통과**
- 3변수는 각각 **외부충격(환율)**, **내수(소비)**, **투자(자본형성)**을 대표
Forward Stepwise 알고리즘:
1. 빈 모형에서 시작
2. AIC가 가장 많이 감소하는 변수를 하나 추가
3. 더 이상 AIC가 감소하지 않으면 중단
**왜 3변수인가?**
**실제 선택된 변수:** LEADING_INDEX, GDP_GROWTH, UNEMPLOYMENT, CD_RATE (4개)
**기대 부호:**
| 변수 | 기대 부호 | 근거 |
|------|-----------|------|
| GDP_GROWTH | + | 경기 호황 → Zt 상승 (신용 개선) |
| UNEMPLOYMENT | - | 실업 증가 → Zt 하락 (부도 증가) |
| LEADING_INDEX | + | 경기 선행지수 상승 → Zt 상승 |
| CD_RATE | - | 금리 상승 → 기업 부담 증가 → Zt 하락 |
- 26개 연간 관측치로는 과적합(overfitting) 방지를 위해 설명변수를 최소화해야 함
- 경험칙: 관측치 수 / 변수 수 ≥ 8 (26/3 ≈ 8.7)
- 3변수는 환율·소비·투자라는 서로 다른 경기 측면을 포착
**왜 OLS인가?**

View File

@@ -166,7 +166,9 @@ def main():
else:
model_input = macro_data
macro_model = build_macro_zt_model(zt_dict, model_input, method="stepwise_aic")
forced_vars = config.get("model", {}).get("macro_vars", None)
macro_model = build_macro_zt_model(zt_dict, model_input, method="stepwise_aic",
forced_vars=forced_vars)
print(f"\n 선택된 변수: {macro_model.selected_vars}")
print(macro_model.summary())

View File

@@ -52,7 +52,8 @@ class MacroZtModel:
zt_series: pd.Series,
macro_data: pd.DataFrame,
method: str = "stepwise_aic",
standardize: bool = True
standardize: bool = False,
forced_vars: Optional[List[str]] = None
) -> "MacroZtModel":
"""
Zt ~ 거시변수 회귀모형 적합
@@ -98,7 +99,14 @@ class MacroZtModel:
X = X.drop(columns=[col])
# 변수 선택
if method == "all":
if forced_vars:
available = [v for v in forced_vars if v in X.columns]
if len(available) != len(forced_vars):
missing = set(forced_vars) - set(available)
logger.warning(f"강제 지정 변수 중 누락: {missing}")
self.selected_vars = available
logger.info(f"강제 지정 변수 사용: {self.selected_vars}")
elif method == "all":
self.selected_vars = list(X.columns)
elif method.startswith("stepwise"):
criterion = "aic" if "aic" in method else "bic"
@@ -280,7 +288,8 @@ class MacroZtModel:
def build_macro_zt_model(
zt_dict: Dict[int, float],
macro_df: pd.DataFrame,
method: str = "stepwise_aic"
method: str = "stepwise_aic",
forced_vars: Optional[List[str]] = None
) -> MacroZtModel:
"""
편의 함수: Zt 딕셔너리 + 거시 DataFrame → 회귀모형 구축
@@ -293,6 +302,8 @@ def build_macro_zt_model(
index=연도, columns=거시변수
method : str
변수 선택 방법
forced_vars : List[str], optional
강제 지정 변수 (지정 시 method 무시)
Returns
-------
@@ -302,6 +313,6 @@ def build_macro_zt_model(
zt_series.index.name = "YEAR"
model = MacroZtModel()
model.fit(zt_series, macro_df, method=method)
model.fit(zt_series, macro_df, method=method, forced_vars=forced_vars)
return model