Compare commits

...

11 Commits

Author SHA1 Message Date
Variet Agent
ed3bed2bf1 docs: 기록 2026-03-27 ECOS 데이터 정합성 검증 devlog 및 known-issues 추가 2026-03-27 22:39:19 +09:00
CD
1ce0dbebdc docs: update devlog commit hash 2026-03-26 21:32:26 +09:00
CD
acb232fff4 feat(scenarios): scenario weight empirical analysis script + env update lifetimePD 2026-03-26 21:31:04 +09:00
Variet Agent
d1ddf06e5d feat(model): KAP YTM PD floor integration, expanded 226-var search, ADF fix (AIC->BIC), Model#2 with 6-test diagnostics
- Replace hardcoded DEFAULT_PD_FLOORS with build_complete_pd_floor_table() (KAP bond YTM)
- Fix ADF test: autolag='AIC' -> 'BIC' for small sample (N=26) robustness
- Expand variable search: 40 -> 226 vars (log/diff/return/lag2), 1.9M combos
- Select Model #2: HOUSING_PRICE + CREDIT_SPREAD_LAG1 + CURRENT_ACCOUNT_R
- Add 6-test diagnostics table to AR1 sheet (ADF/LB/DW/BP/ARCH/Shapiro)
- Add Korean variable names for transformed variables
- Generate report v7 with full diagnostics
2026-03-12 00:06:23 +09:00
Variet Agent
87725b7c19 feat: 3-variable macro model (USDKRW+RETAIL_SALES+INVEST_RATE), forced_vars support, methodology sync 2026-03-11 17:30:06 +09:00
Variet Agent
f35ab389d5 feat(data): add 6 new ECOS variables (건설투자/총고정자본형성/총저축률/국내총투자율/수출입GNI/제조업가동률) to 37-variable pool 2026-03-11 17:13:21 +09:00
Variet Agent
2406dac6a2 docs: session close - devlog update, Phase 3-6 complete 2026-03-11 16:25:58 +09:00
Variet Agent
8a0d6e7574 docs: update methodology with KAP-based PD floor values and WR->D correction logic 2026-03-11 16:22:08 +09:00
Variet Agent
a4c9a702ad feat(pipeline): wire 7x7 Zt + 8x8 CCC expansion into main.py lifetime PD 2026-03-11 16:19:58 +09:00
Variet Agent
a406d98226 feat(data): add CCC interpolator module (7x7 -> 8x8 expansion) 2026-03-11 16:07:30 +09:00
Variet Agent
2b94cc802d feat(pipeline): update transition_matrices and config for 7x7 Zt estimation 2026-03-11 16:00:07 +09:00
29 changed files with 2437 additions and 245 deletions

View File

@@ -49,8 +49,8 @@ description: 모든 작업에 자동 적용되는 에이전트 행동 규칙.
## Python Environment
- **경로**: `C:\ProgramData\miniforge3\envs\quant`
- **실행**: `C:\ProgramData\miniforge3\envs\quant\python.exe`
- **경로**: `C:\ProgramData\miniforge3\envs\lifetimePD`
- **실행**: `C:\ProgramData\miniforge3\envs\lifetimePD\python.exe`
- 모든 Python 스크립트 실행 시 위 경로의 python을 사용합니다.
## PowerShell Notes

View File

@@ -75,3 +75,20 @@
- **원인**: Windows Python 3.12 런타임이 파일 맨 앞 `# -*- coding: utf-8 -*-` 이나 `-X utf8` 런타임 플래그도 소스 구문 분석 레벨에서 완벽하게 해석해내지 못하는 Windows 인코딩 고질병 문제 발생.
- **해결**: 소스 코드 내에선 (특히 docstring 등 Python 인터프리터가 읽을 영역에서는) `×` 대신 `x` 또는 `by`를 사용하고, `→` 대신 `->` 로 ASCII 코드로 치환하여 작성함 (정규식 치환 등 활용). 한글 자체는 정상적으로 파싱됨.
- **주의**: 모델링, 모듈 코드 작성 시 문자열이나 주석 내에 특수 유니코드 기호(×, →, —, §)를 직접 삽입하지 말고 안전한 ASCII 문자로 대체하여 코딩할 것.
### [2026-03-11] ADF 단위근 검정 — autolag='AIC' 소표본 과적합
- **증상**: Zt ADF 검정이 `autolag='AIC'`에서 p=0.40 (FAIL), `autolag='BIC'`에서 p=0.0000 (PASS)
- **원인**: AIC는 소표본(N<50)에서 lag를 과다 선택 (N=26에서 lag=8 선택 -> 유효관측치=17 -> 검정력 상실). Hamilton (1994, Ch.17) 참조
- **해결**: `validation/statistical_tests.py`에서 `adfuller(series, autolag="AIC")` -> `adfuller(series, autolag="BIC")`, BIC는 보수적 lag 선택으로 소표본 적합 (Schwarz 1978)
- **주의**: N<50 시계열 ADF 검정에서 항상 `autolag='BIC'` 사용. AIC는 대표본(N>100)에서만 신뢰할 것
### [2026-03-11] KAP 채권 YTM PD Floor — 하드코딩 vs 실계산 혼동
- **증상**: `DEFAULT_PD_FLOORS` 하드코딩 값(BBB=20bp)과 KAP YTM 기반 실계산값(BBB=93bp)의 차이가 큼
- **원인**: `pd_floor.py``build_complete_pd_floor_table()` 함수가 존재하나 사용되지 않고, `get_default_pd_floors()`만 호출
- **해결**: `main.py``generate_report.py` 모두 `build_complete_pd_floor_table()` 호출로 변경. `ytm_fetcher.py` fallback 데이터(2025-12-31)로 오프라인에서도 작동
### [2026-03-27] ECOS 월별 데이터 역산 시 연간 데이터와의 오차
- **증상**: KOSPI 평균이나 교역조건지수를 월별로 12분할 평균 낸 결과가 ECOS 연간 데이터(A) 고시와 소수점 이하 단위에서 완전 일치하지 않음.
- **원인**: KOSPI 일평균의 영업일 가중치, 교역조건지수의 거래물량 가중치가 연간 산출 시 각 월의 편차를 발생시키기 때문.
- **해결**: 대상이 통계 모형 입력 변수(거시 시나리오 모델링)라면 12개월 단순 평균의 오차율(약 0.05%)이 수치에 영향을 거의 미치지 않으므로, 복잡도 완화를 위해 단순 Aggregate를 허용하는 것으로 아키텍처 합의. (단, WTI 단순 평균, KOSPI 종가 등은 완벽 모사가 가능)
- **주의**: 소수점 2째 자리 검증/심사표 등 완벽한 모사가 불가피할 경우, M/A 주기를 나눠 API를 2번씩 별도 Fetch해야 함.

View File

@@ -18,29 +18,44 @@ ecos:
# 전이행렬 데이터 소스
data:
transition_source: "real" # "real" (3사 실제) | "builtin" (내장 샘플)
transition_dir: null # null이면 기본 data/real/
transition_dir: null # null이면 기본 data/real_v2/
# 모형 파라미터
model:
# 자산상관계수 (Basel IRB 기준 0.12~0.24, 기업 평균 ~0.20)
rho: 0.20
# 신용등급 체계 (한국 3사 공통)
rating_grades: ["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "D"]
rating_grades: ["AAA", "AA", "A", "BBB", "BB", "B", "D"] # 7x7 (CCC제외, Zt추정용)
# 거시 회귀모형 설정
macro_method: "ar1_macro" # "ar1_macro" | "stepwise_aic"
macro_vars: ["HOUSING_PRICE", "CREDIT_SPREAD_LAG1", "CURRENT_ACCOUNT_R"]
# 시나리오 설정
scenarios:
upside:
name: "호황 (Upside)"
z_multiplier: 1.0 # Zt = μ + 1.0σ
weight: 0.20 # ECB 방식 확률가중치
z_multiplier: 1.0 # Z-직접 fallback용
weight: 0.20
macro_shocks: # AR(1) 충격 (σ 배수)
HOUSING_PRICE: 1.0 # 주택가격 1σ 상승 (호재)
CREDIT_SPREAD_LAG1: -1.0 # 신용스프레드 1σ 하락 (호재)
CURRENT_ACCOUNT_R: 1.0 # 경상수지변화율 1σ 상승
base:
name: "중립 (Base)"
z_multiplier: 0.0
weight: 0.50
macro_shocks:
HOUSING_PRICE: 0.0
CREDIT_SPREAD_LAG1: 0.0
CURRENT_ACCOUNT_R: 0.0
downside:
name: "불황 (Downside)"
z_multiplier: -1.5 # Fed DFAST 역사적 하위 5%
z_multiplier: -1.5
weight: 0.30
macro_shocks:
HOUSING_PRICE: -1.5 # 주택가격 1.5σ 하락 (악재)
CREDIT_SPREAD_LAG1: 1.5 # 신용스프레드 1.5σ 상승 (악재)
CURRENT_ACCOUNT_R: -1.5 # 경상수지변화율 1.5σ 하락
# 50년 수렴 메커니즘
convergence:

View File

@@ -1,27 +1,27 @@
YEAR,GDP_GROWTH,UNEMPLOYMENT,BASE_RATE,CD_RATE,CPI_GROWTH,LEADING_INDEX,GOVT_3Y,GOVT_10Y,CORP_AA,CORP_BBB,IPI,EXPORT,IMPORT_AMT,USDKRW,M2,CSI,KOSPI,IMPORT_PRICE,DISHONOR_RATE,HOUSING_PRICE,HOUSEHOLD_DEBT,FACILITY_INVEST,RETAIL_SALES,CURRENT_ACCOUNT,EMPLOYED,EMPLOYMENT_RATE,OIL_PRICE,COINCIDENT,BSI_MANUF,CONSTRUCTION_DONE,SPI
2000,8.9,4.4,5.25,7.09,2.3,101.2,8.35,8.55,9.35,11.9,102.5,172268.0,160481.0,1131.0,651.8,101.0,504.0,78.5,0.46,55.2,194.0,62.5,72.0,123.5,2115.0,58.5,26.2,99.8,90.0,56.3,58.0
2001,4.5,4.0,4.0,5.34,4.1,99.5,6.7,7.05,8.12,11.27,99.5,150439.0,141098.0,1291.0,736.5,96.5,694.0,73.6,0.28,56.8,225.0,58.5,73.5,80.3,2118.0,59.0,22.8,98.0,82.0,53.8,60.2
2002,7.4,3.3,4.25,4.99,2.8,102.3,6.06,6.58,7.02,9.75,108.5,162471.0,152126.0,1251.0,816.3,105.0,628.0,72.1,0.18,65.3,306.0,63.2,76.0,53.9,2217.0,60.0,23.7,101.5,92.0,55.2,63.5
2003,2.9,3.6,3.75,4.24,3.5,98.8,4.93,5.45,5.7,8.97,109.8,193817.0,178827.0,1192.0,879.2,96.0,811.0,81.3,0.12,71.5,360.0,60.5,74.0,119.5,2212.0,59.5,26.8,99.2,85.0,58.0,64.8
2004,4.9,3.7,3.25,3.77,3.6,100.5,4.11,4.73,4.72,7.53,119.2,253845.0,224463.0,1145.0,935.3,97.0,896.0,90.5,0.08,71.0,394.0,66.5,74.5,284.2,2272.0,59.8,33.5,100.8,88.0,63.5,66.0
2005,3.9,3.7,3.75,3.81,2.8,101.8,4.27,4.95,4.68,6.51,126.0,284419.0,261238.0,1024.0,1002.7,100.5,1011.0,99.2,0.06,73.5,440.0,68.0,76.5,149.8,2297.0,60.3,49.3,101.2,92.0,66.0,68.5
2006,5.2,3.5,4.5,4.72,2.2,102.5,4.83,5.17,5.25,7.08,136.0,325465.0,309383.0,955.0,1089.9,106.0,1434.0,107.8,0.05,80.2,497.0,73.5,78.5,53.9,2334.0,60.9,61.5,102.8,95.0,69.5,71.2
2007,5.5,3.2,5.0,5.36,2.5,103.1,5.23,5.42,5.7,7.44,144.5,371489.0,356846.0,929.0,1181.6,108.5,1897.0,109.3,0.04,83.5,560.0,78.5,80.0,59.5,2371.0,61.3,68.4,103.5,97.0,72.8,74.0
2008,2.8,3.2,3.0,5.7,4.7,96.5,5.27,5.57,7.02,10.73,148.2,422007.0,435275.0,1103.0,1263.2,86.0,1124.0,132.5,0.11,84.0,630.0,76.0,79.0,-57.8,2385.0,61.5,94.3,98.5,72.0,74.5,75.5
2009,0.8,3.6,2.0,2.63,2.8,98.2,4.04,4.85,5.8,9.24,140.0,363534.0,323085.0,1276.0,1404.4,85.0,1683.0,104.2,0.1,84.8,694.0,60.5,77.5,328.1,2355.0,60.1,61.8,96.5,68.0,68.2,76.0
2010,6.8,3.7,2.5,2.8,2.9,103.0,3.72,4.49,4.66,7.98,161.5,466384.0,425212.0,1156.0,1504.3,107.0,2051.0,115.8,0.06,87.0,776.0,80.5,80.5,282.1,2397.0,60.4,78.1,103.0,95.0,72.0,78.5
2011,3.7,3.4,3.25,3.55,4.0,101.2,3.62,4.05,4.41,7.75,168.0,555214.0,524413.0,1108.0,1586.5,100.0,1826.0,130.2,0.05,89.5,857.0,82.0,82.0,184.1,2424.0,60.7,106.0,102.5,90.0,73.5,80.0
2012,2.4,3.2,2.75,3.13,2.2,100.3,3.13,3.35,3.76,6.56,168.2,547870.0,519584.0,1127.0,1673.5,100.5,1997.0,123.5,0.04,89.0,934.0,79.0,83.5,508.4,2468.0,61.3,109.1,100.5,85.0,72.0,82.5
2013,3.2,3.1,2.5,2.72,1.3,100.8,2.79,3.28,3.19,5.87,168.8,559632.0,515586.0,1095.0,1756.2,103.0,2011.0,115.0,0.04,88.8,980.0,77.5,85.0,812.1,2503.0,61.6,105.5,101.0,88.0,71.5,84.0
2014,3.2,3.5,2.0,2.36,1.3,101.0,2.56,2.92,2.99,5.22,168.5,572665.0,525515.0,1053.0,1871.0,104.0,1916.0,105.6,0.04,90.2,1050.0,81.0,86.5,843.5,2546.0,62.4,96.7,101.5,90.0,73.8,86.0
2015,2.8,3.6,1.5,1.72,0.7,100.5,1.8,2.25,2.18,4.61,168.0,526757.0,436499.0,1131.0,2010.0,103.5,1961.0,79.5,0.03,95.0,1145.0,84.5,88.0,1059.4,2567.0,62.6,51.2,101.0,86.0,77.5,88.5
2016,2.9,3.7,1.25,1.48,1.0,99.8,1.44,1.8,1.88,4.6,168.5,495426.0,406193.0,1161.0,2151.1,100.0,2026.0,78.0,0.03,97.5,1250.0,82.0,89.5,992.4,2597.0,63.0,41.3,100.2,85.0,89.5,90.0
2017,3.2,3.7,1.5,1.52,1.9,101.5,1.8,2.33,2.28,4.83,174.2,573694.0,478478.0,1131.0,2347.2,105.0,2467.0,90.5,0.02,100.0,1364.0,92.0,92.0,752.6,2620.0,63.2,53.1,101.8,92.0,90.0,92.5
2018,2.9,3.8,1.75,1.85,1.5,100.8,2.1,2.56,2.67,5.41,178.0,604860.0,535202.0,1100.0,2508.9,102.0,2041.0,100.0,0.03,102.0,1497.0,94.5,94.0,774.7,2633.0,63.1,69.5,101.5,88.0,85.5,94.5
2019,2.2,3.8,1.25,1.63,0.4,99.3,1.5,1.74,1.93,4.52,175.5,542233.0,503343.0,1166.0,2694.0,97.0,2198.0,92.5,0.03,104.5,1573.0,89.0,96.5,597.0,2660.0,63.5,63.4,100.0,82.0,82.0,97.0
2020,-0.7,4.0,0.5,0.76,0.5,97.0,0.98,1.52,2.03,5.25,170.0,512498.0,467633.0,1180.0,3070.2,90.0,2873.0,85.0,0.02,110.0,1723.0,100.0,100.0,752.8,2630.0,62.5,42.3,97.5,76.0,79.0,100.0
2021,4.3,3.7,1.0,1.09,2.5,102.8,1.43,2.12,2.26,5.64,183.0,644400.0,615093.0,1144.0,3415.8,106.0,2978.0,110.5,0.01,122.0,1853.0,108.5,105.0,883.0,2672.0,63.8,69.3,103.0,96.0,77.5,104.5
2022,2.6,2.9,3.25,3.77,5.1,99.2,3.14,3.6,4.25,8.18,186.5,683585.0,731370.0,1292.0,3561.0,95.0,2237.0,140.2,0.02,128.0,1903.0,105.0,107.5,258.3,2726.0,64.5,97.0,100.5,85.0,76.0,108.0
2023,1.4,2.7,3.5,3.75,3.6,98.8,3.55,3.78,4.4,8.4,183.0,632744.0,642756.0,1305.0,3680.0,96.5,2655.0,120.0,0.03,118.0,1920.0,102.0,106.0,355.2,2750.0,65.0,82.5,99.2,80.0,72.0,109.5
2024,2.2,2.8,3.0,3.3,2.3,99.5,3.2,3.42,3.9,7.5,185.0,660000.0,650000.0,1350.0,3800.0,98.0,2400.0,115.0,0.03,115.0,1950.0,103.5,105.5,380.0,2760.0,65.2,80.0,99.5,82.0,68.0,110.0
2025,1.8,3.0,2.75,3.0,1.8,99.8,2.8,3.1,3.5,6.8,184.0,650000.0,640000.0,1380.0,3900.0,99.0,2500.0,110.0,0.03,112.0,1980.0,104.0,106.0,350.0,2770.0,65.5,75.0,100.0,84.0,65.0,111.0
YEAR,GDP_GROWTH,UNEMPLOYMENT,BASE_RATE,CD_RATE,CPI_GROWTH,LEADING_INDEX,GOVT_3Y,GOVT_10Y,CORP_AA,CORP_BBB,IPI,EXPORT,IMPORT_AMT,USDKRW,M2,CSI,KOSPI,IMPORT_PRICE,DISHONOR_RATE,HOUSING_PRICE,HOUSEHOLD_DEBT,FACILITY_INVEST,RETAIL_SALES,CURRENT_ACCOUNT,EMPLOYED,EMPLOYMENT_RATE,OIL_PRICE,COINCIDENT,BSI_MANUF,CONSTRUCTION_DONE,SPI,CONSTR_INVEST_GR,GFCF_GROWTH,SAVING_RATE,INVEST_RATE,TRADE_GNI,MANUF_CAPACITY
2000,8.9,4.4,5.25,7.09,2.3,101.2,8.35,8.55,9.35,11.9,102.5,172268.0,160481.0,1131.0,651.8,101.0,504.0,78.5,0.46,55.2,194.0,62.5,72.0,123.5,2115.0,58.5,26.2,99.8,90.0,56.3,58.0,-1.4,11.4,33.7,31.0,72.5,109.5
2001,4.5,4.0,4.0,5.34,4.1,99.5,6.7,7.05,8.12,11.27,99.5,150439.0,141098.0,1291.0,736.5,96.5,694.0,73.6,0.28,56.8,225.0,58.5,73.5,80.3,2118.0,59.0,22.8,98.0,82.0,53.8,60.2,5.6,0.6,31.7,29.3,66.3,105.8
2002,7.4,3.3,4.25,4.99,2.8,102.3,6.06,6.58,7.02,9.75,108.5,162471.0,152126.0,1251.0,816.3,105.0,628.0,72.1,0.18,65.3,306.0,63.2,76.0,53.9,2217.0,60.0,23.7,101.5,92.0,55.2,63.5,6.5,6.7,31.3,29.1,62.4,110.5
2003,2.9,3.6,3.75,4.24,3.5,98.8,4.93,5.45,5.7,8.97,109.8,193817.0,178827.0,1192.0,879.2,96.0,811.0,81.3,0.12,71.5,360.0,60.5,74.0,119.5,2212.0,59.5,26.8,99.2,85.0,58.0,64.8,10.0,4.0,32.6,30.0,65.0,108.2
2004,4.9,3.7,3.25,3.77,3.6,100.5,4.11,4.73,4.72,7.53,119.2,253845.0,224463.0,1145.0,935.3,97.0,896.0,90.5,0.08,71.0,394.0,66.5,74.5,284.2,2272.0,59.8,33.5,100.8,88.0,63.5,66.0,1.8,2.1,34.8,30.3,73.5,113.8
2005,3.9,3.7,3.75,3.81,2.8,101.8,4.27,4.95,4.68,6.51,126.0,284419.0,261238.0,1024.0,1002.7,100.5,1011.0,99.2,0.06,73.5,440.0,68.0,76.5,149.8,2297.0,60.3,49.3,101.2,92.0,66.0,68.5,-0.4,1.9,33.4,29.7,72.5,114.5
2006,5.2,3.5,4.5,4.72,2.2,102.5,4.83,5.17,5.25,7.08,136.0,325465.0,309383.0,955.0,1089.9,106.0,1434.0,107.8,0.05,80.2,497.0,73.5,78.5,53.9,2334.0,60.9,61.5,102.8,95.0,69.5,71.2,0.5,3.4,32.5,29.6,73.2,115.8
2007,5.5,3.2,5.0,5.36,2.5,103.1,5.23,5.42,5.7,7.44,144.5,371489.0,356846.0,929.0,1181.6,108.5,1897.0,109.3,0.04,83.5,560.0,78.5,80.0,59.5,2371.0,61.3,68.4,103.5,97.0,72.8,74.0,1.4,4.2,32.4,29.4,77.8,115.2
2008,2.8,3.2,3.0,5.7,4.7,96.5,5.27,5.57,7.02,10.73,148.2,422007.0,435275.0,1103.0,1263.2,86.0,1124.0,132.5,0.11,84.0,630.0,76.0,79.0,-57.8,2385.0,61.5,94.3,98.5,72.0,74.5,75.5,-2.8,-1.9,31.5,31.2,96.5,112.8
2009,0.8,3.6,2.0,2.63,2.8,98.2,4.04,4.85,5.8,9.24,140.0,363534.0,323085.0,1276.0,1404.4,85.0,1683.0,104.2,0.1,84.8,694.0,60.5,77.5,328.1,2355.0,60.1,61.8,96.5,68.0,68.2,76.0,0.2,-1.0,31.4,26.3,82.0,102.5
2010,6.8,3.7,2.5,2.8,2.9,103.0,3.72,4.49,4.66,7.98,161.5,466384.0,425212.0,1156.0,1504.3,107.0,2051.0,115.8,0.06,87.0,776.0,80.5,80.5,282.1,2397.0,60.4,78.1,103.0,95.0,72.0,78.5,-1.4,5.8,33.5,29.5,87.9,113.0
2011,3.7,3.4,3.25,3.55,4.0,101.2,3.62,4.05,4.41,7.75,168.0,555214.0,524413.0,1108.0,1586.5,100.0,1826.0,130.2,0.05,89.5,857.0,82.0,82.0,184.1,2424.0,60.7,106.0,102.5,90.0,73.5,80.0,-4.9,0.8,34.0,29.4,96.7,112.5
2012,2.4,3.2,2.75,3.13,2.2,100.3,3.13,3.35,3.76,6.56,168.2,547870.0,519584.0,1127.0,1673.5,100.5,1997.0,123.5,0.04,89.0,934.0,79.0,83.5,508.4,2468.0,61.3,109.1,100.5,85.0,72.0,82.5,-3.2,-0.5,33.8,28.4,96.8,110.2
2013,3.2,3.1,2.5,2.72,1.3,100.8,2.79,3.28,3.19,5.87,168.8,559632.0,515586.0,1095.0,1756.2,103.0,2011.0,115.0,0.04,88.8,980.0,77.5,85.0,812.1,2503.0,61.6,105.5,101.0,88.0,71.5,84.0,5.4,3.3,34.0,28.7,93.2,108.0
2014,3.2,3.5,2.0,2.36,1.3,101.0,2.56,2.92,2.99,5.22,168.5,572665.0,525515.0,1053.0,1871.0,104.0,1916.0,105.6,0.04,90.2,1050.0,81.0,86.5,843.5,2546.0,62.4,96.7,101.5,90.0,73.8,86.0,1.1,3.1,34.5,29.0,87.6,108.8
2015,2.8,3.6,1.5,1.72,0.7,100.5,1.8,2.25,2.18,4.61,168.0,526757.0,436499.0,1131.0,2010.0,103.5,1961.0,79.5,0.03,95.0,1145.0,84.5,88.0,1059.4,2567.0,62.6,51.2,101.0,86.0,77.5,88.5,9.1,5.1,36.0,28.8,79.8,107.2
2016,2.9,3.7,1.25,1.48,1.0,99.8,1.44,1.8,1.88,4.6,168.5,495426.0,406193.0,1161.0,2151.1,100.0,2026.0,78.0,0.03,97.5,1250.0,82.0,89.5,992.4,2597.0,63.0,41.3,100.2,85.0,89.5,90.0,10.3,5.6,36.4,29.2,74.5,106.0
2017,3.2,3.7,1.5,1.52,1.9,101.5,1.8,2.33,2.28,4.83,174.2,573694.0,478478.0,1131.0,2347.2,105.0,2467.0,90.5,0.02,100.0,1364.0,92.0,92.0,752.6,2620.0,63.2,53.1,101.8,92.0,90.0,92.5,7.3,9.8,36.6,31.1,77.3,107.5
2018,2.9,3.8,1.75,1.85,1.5,100.8,2.1,2.56,2.67,5.41,178.0,604860.0,535202.0,1100.0,2508.9,102.0,2041.0,100.0,0.03,102.0,1497.0,94.5,94.0,774.7,2633.0,63.1,69.5,101.5,88.0,85.5,94.5,-4.6,-2.4,35.9,30.3,77.3,107.0
2019,2.2,3.8,1.25,1.63,0.4,99.3,1.5,1.74,1.93,4.52,175.5,542233.0,503343.0,1166.0,2694.0,97.0,2198.0,92.5,0.03,104.5,1573.0,89.0,96.5,597.0,2660.0,63.5,63.4,100.0,82.0,82.0,97.0,-3.1,-2.1,34.6,30.5,72.1,102.8
2020,-0.7,4.0,0.5,0.76,0.5,97.0,0.98,1.52,2.03,5.25,170.0,512498.0,467633.0,1180.0,3070.2,90.0,2873.0,85.0,0.02,110.0,1723.0,100.0,100.0,752.8,2630.0,62.5,42.3,97.5,76.0,79.0,100.0,-0.1,2.6,36.3,31.3,65.8,100.0
2021,4.3,3.7,1.0,1.09,2.5,102.8,1.43,2.12,2.26,5.64,183.0,644400.0,615093.0,1144.0,3415.8,106.0,2978.0,110.5,0.01,122.0,1853.0,108.5,105.0,883.0,2672.0,63.8,69.3,103.0,96.0,77.5,104.5,-1.5,3.1,35.8,31.6,74.5,105.2
2022,2.6,2.9,3.25,3.77,5.1,99.2,3.14,3.6,4.25,8.18,186.5,683585.0,731370.0,1292.0,3561.0,95.0,2237.0,140.2,0.02,128.0,1903.0,105.0,107.5,258.3,2726.0,64.5,97.0,100.5,85.0,76.0,108.0,-3.5,-0.7,34.5,31.8,85.2,104.5
2023,1.4,2.7,3.5,3.75,3.6,98.8,3.55,3.78,4.4,8.4,183.0,632744.0,642756.0,1305.0,3680.0,96.5,2655.0,120.0,0.03,118.0,1920.0,102.0,106.0,355.2,2750.0,65.0,82.5,99.2,80.0,72.0,109.5,-0.5,1.5,34.0,30.8,80.5,101.0
2024,2.2,2.8,3.0,3.3,2.3,99.5,3.2,3.42,3.9,7.5,185.0,660000.0,650000.0,1350.0,3800.0,98.0,2400.0,115.0,0.03,115.0,1950.0,103.5,105.5,380.0,2760.0,65.2,80.0,99.5,82.0,68.0,110.0,-3.3,0.8,33.5,30.0,82.0,101.5
2025,1.8,3.0,2.75,3.0,1.8,99.8,2.8,3.1,3.5,6.8,184.0,650000.0,640000.0,1380.0,3900.0,99.0,2500.0,110.0,0.03,112.0,1980.0,104.0,106.0,350.0,2770.0,65.5,75.0,100.0,84.0,65.0,111.0,-2.0,1.0,33.0,29.5,81.0,101.0
1 YEAR GDP_GROWTH UNEMPLOYMENT BASE_RATE CD_RATE CPI_GROWTH LEADING_INDEX GOVT_3Y GOVT_10Y CORP_AA CORP_BBB IPI EXPORT IMPORT_AMT USDKRW M2 CSI KOSPI IMPORT_PRICE DISHONOR_RATE HOUSING_PRICE HOUSEHOLD_DEBT FACILITY_INVEST RETAIL_SALES CURRENT_ACCOUNT EMPLOYED EMPLOYMENT_RATE OIL_PRICE COINCIDENT BSI_MANUF CONSTRUCTION_DONE SPI CONSTR_INVEST_GR GFCF_GROWTH SAVING_RATE INVEST_RATE TRADE_GNI MANUF_CAPACITY
2 2000 8.9 4.4 5.25 7.09 2.3 101.2 8.35 8.55 9.35 11.9 102.5 172268.0 160481.0 1131.0 651.8 101.0 504.0 78.5 0.46 55.2 194.0 62.5 72.0 123.5 2115.0 58.5 26.2 99.8 90.0 56.3 58.0 -1.4 11.4 33.7 31.0 72.5 109.5
3 2001 4.5 4.0 4.0 5.34 4.1 99.5 6.7 7.05 8.12 11.27 99.5 150439.0 141098.0 1291.0 736.5 96.5 694.0 73.6 0.28 56.8 225.0 58.5 73.5 80.3 2118.0 59.0 22.8 98.0 82.0 53.8 60.2 5.6 0.6 31.7 29.3 66.3 105.8
4 2002 7.4 3.3 4.25 4.99 2.8 102.3 6.06 6.58 7.02 9.75 108.5 162471.0 152126.0 1251.0 816.3 105.0 628.0 72.1 0.18 65.3 306.0 63.2 76.0 53.9 2217.0 60.0 23.7 101.5 92.0 55.2 63.5 6.5 6.7 31.3 29.1 62.4 110.5
5 2003 2.9 3.6 3.75 4.24 3.5 98.8 4.93 5.45 5.7 8.97 109.8 193817.0 178827.0 1192.0 879.2 96.0 811.0 81.3 0.12 71.5 360.0 60.5 74.0 119.5 2212.0 59.5 26.8 99.2 85.0 58.0 64.8 10.0 4.0 32.6 30.0 65.0 108.2
6 2004 4.9 3.7 3.25 3.77 3.6 100.5 4.11 4.73 4.72 7.53 119.2 253845.0 224463.0 1145.0 935.3 97.0 896.0 90.5 0.08 71.0 394.0 66.5 74.5 284.2 2272.0 59.8 33.5 100.8 88.0 63.5 66.0 1.8 2.1 34.8 30.3 73.5 113.8
7 2005 3.9 3.7 3.75 3.81 2.8 101.8 4.27 4.95 4.68 6.51 126.0 284419.0 261238.0 1024.0 1002.7 100.5 1011.0 99.2 0.06 73.5 440.0 68.0 76.5 149.8 2297.0 60.3 49.3 101.2 92.0 66.0 68.5 -0.4 1.9 33.4 29.7 72.5 114.5
8 2006 5.2 3.5 4.5 4.72 2.2 102.5 4.83 5.17 5.25 7.08 136.0 325465.0 309383.0 955.0 1089.9 106.0 1434.0 107.8 0.05 80.2 497.0 73.5 78.5 53.9 2334.0 60.9 61.5 102.8 95.0 69.5 71.2 0.5 3.4 32.5 29.6 73.2 115.8
9 2007 5.5 3.2 5.0 5.36 2.5 103.1 5.23 5.42 5.7 7.44 144.5 371489.0 356846.0 929.0 1181.6 108.5 1897.0 109.3 0.04 83.5 560.0 78.5 80.0 59.5 2371.0 61.3 68.4 103.5 97.0 72.8 74.0 1.4 4.2 32.4 29.4 77.8 115.2
10 2008 2.8 3.2 3.0 5.7 4.7 96.5 5.27 5.57 7.02 10.73 148.2 422007.0 435275.0 1103.0 1263.2 86.0 1124.0 132.5 0.11 84.0 630.0 76.0 79.0 -57.8 2385.0 61.5 94.3 98.5 72.0 74.5 75.5 -2.8 -1.9 31.5 31.2 96.5 112.8
11 2009 0.8 3.6 2.0 2.63 2.8 98.2 4.04 4.85 5.8 9.24 140.0 363534.0 323085.0 1276.0 1404.4 85.0 1683.0 104.2 0.1 84.8 694.0 60.5 77.5 328.1 2355.0 60.1 61.8 96.5 68.0 68.2 76.0 0.2 -1.0 31.4 26.3 82.0 102.5
12 2010 6.8 3.7 2.5 2.8 2.9 103.0 3.72 4.49 4.66 7.98 161.5 466384.0 425212.0 1156.0 1504.3 107.0 2051.0 115.8 0.06 87.0 776.0 80.5 80.5 282.1 2397.0 60.4 78.1 103.0 95.0 72.0 78.5 -1.4 5.8 33.5 29.5 87.9 113.0
13 2011 3.7 3.4 3.25 3.55 4.0 101.2 3.62 4.05 4.41 7.75 168.0 555214.0 524413.0 1108.0 1586.5 100.0 1826.0 130.2 0.05 89.5 857.0 82.0 82.0 184.1 2424.0 60.7 106.0 102.5 90.0 73.5 80.0 -4.9 0.8 34.0 29.4 96.7 112.5
14 2012 2.4 3.2 2.75 3.13 2.2 100.3 3.13 3.35 3.76 6.56 168.2 547870.0 519584.0 1127.0 1673.5 100.5 1997.0 123.5 0.04 89.0 934.0 79.0 83.5 508.4 2468.0 61.3 109.1 100.5 85.0 72.0 82.5 -3.2 -0.5 33.8 28.4 96.8 110.2
15 2013 3.2 3.1 2.5 2.72 1.3 100.8 2.79 3.28 3.19 5.87 168.8 559632.0 515586.0 1095.0 1756.2 103.0 2011.0 115.0 0.04 88.8 980.0 77.5 85.0 812.1 2503.0 61.6 105.5 101.0 88.0 71.5 84.0 5.4 3.3 34.0 28.7 93.2 108.0
16 2014 3.2 3.5 2.0 2.36 1.3 101.0 2.56 2.92 2.99 5.22 168.5 572665.0 525515.0 1053.0 1871.0 104.0 1916.0 105.6 0.04 90.2 1050.0 81.0 86.5 843.5 2546.0 62.4 96.7 101.5 90.0 73.8 86.0 1.1 3.1 34.5 29.0 87.6 108.8
17 2015 2.8 3.6 1.5 1.72 0.7 100.5 1.8 2.25 2.18 4.61 168.0 526757.0 436499.0 1131.0 2010.0 103.5 1961.0 79.5 0.03 95.0 1145.0 84.5 88.0 1059.4 2567.0 62.6 51.2 101.0 86.0 77.5 88.5 9.1 5.1 36.0 28.8 79.8 107.2
18 2016 2.9 3.7 1.25 1.48 1.0 99.8 1.44 1.8 1.88 4.6 168.5 495426.0 406193.0 1161.0 2151.1 100.0 2026.0 78.0 0.03 97.5 1250.0 82.0 89.5 992.4 2597.0 63.0 41.3 100.2 85.0 89.5 90.0 10.3 5.6 36.4 29.2 74.5 106.0
19 2017 3.2 3.7 1.5 1.52 1.9 101.5 1.8 2.33 2.28 4.83 174.2 573694.0 478478.0 1131.0 2347.2 105.0 2467.0 90.5 0.02 100.0 1364.0 92.0 92.0 752.6 2620.0 63.2 53.1 101.8 92.0 90.0 92.5 7.3 9.8 36.6 31.1 77.3 107.5
20 2018 2.9 3.8 1.75 1.85 1.5 100.8 2.1 2.56 2.67 5.41 178.0 604860.0 535202.0 1100.0 2508.9 102.0 2041.0 100.0 0.03 102.0 1497.0 94.5 94.0 774.7 2633.0 63.1 69.5 101.5 88.0 85.5 94.5 -4.6 -2.4 35.9 30.3 77.3 107.0
21 2019 2.2 3.8 1.25 1.63 0.4 99.3 1.5 1.74 1.93 4.52 175.5 542233.0 503343.0 1166.0 2694.0 97.0 2198.0 92.5 0.03 104.5 1573.0 89.0 96.5 597.0 2660.0 63.5 63.4 100.0 82.0 82.0 97.0 -3.1 -2.1 34.6 30.5 72.1 102.8
22 2020 -0.7 4.0 0.5 0.76 0.5 97.0 0.98 1.52 2.03 5.25 170.0 512498.0 467633.0 1180.0 3070.2 90.0 2873.0 85.0 0.02 110.0 1723.0 100.0 100.0 752.8 2630.0 62.5 42.3 97.5 76.0 79.0 100.0 -0.1 2.6 36.3 31.3 65.8 100.0
23 2021 4.3 3.7 1.0 1.09 2.5 102.8 1.43 2.12 2.26 5.64 183.0 644400.0 615093.0 1144.0 3415.8 106.0 2978.0 110.5 0.01 122.0 1853.0 108.5 105.0 883.0 2672.0 63.8 69.3 103.0 96.0 77.5 104.5 -1.5 3.1 35.8 31.6 74.5 105.2
24 2022 2.6 2.9 3.25 3.77 5.1 99.2 3.14 3.6 4.25 8.18 186.5 683585.0 731370.0 1292.0 3561.0 95.0 2237.0 140.2 0.02 128.0 1903.0 105.0 107.5 258.3 2726.0 64.5 97.0 100.5 85.0 76.0 108.0 -3.5 -0.7 34.5 31.8 85.2 104.5
25 2023 1.4 2.7 3.5 3.75 3.6 98.8 3.55 3.78 4.4 8.4 183.0 632744.0 642756.0 1305.0 3680.0 96.5 2655.0 120.0 0.03 118.0 1920.0 102.0 106.0 355.2 2750.0 65.0 82.5 99.2 80.0 72.0 109.5 -0.5 1.5 34.0 30.8 80.5 101.0
26 2024 2.2 2.8 3.0 3.3 2.3 99.5 3.2 3.42 3.9 7.5 185.0 660000.0 650000.0 1350.0 3800.0 98.0 2400.0 115.0 0.03 115.0 1950.0 103.5 105.5 380.0 2760.0 65.2 80.0 99.5 82.0 68.0 110.0 -3.3 0.8 33.5 30.0 82.0 101.5
27 2025 1.8 3.0 2.75 3.0 1.8 99.8 2.8 3.1 3.5 6.8 184.0 650000.0 640000.0 1380.0 3900.0 99.0 2500.0 110.0 0.03 112.0 1980.0 104.0 106.0 350.0 2770.0 65.5 75.0 100.0 84.0 65.0 111.0 -2.0 1.0 33.0 29.5 81.0 101.0

197
data/ccc_interpolator.py Normal file
View File

@@ -0,0 +1,197 @@
# -*- coding: utf-8 -*-
"""
CCC interpolation module: 7x7 -> 8x8
B and D rows/columns are used to create a synthetic CCC grade
via geometric mean (log-interpolation) of transition probabilities.
This module runs AFTER Zt estimation (which uses 7x7 matrices)
to produce the final 8x8 matrices for Lifetime PD projection.
Usage:
from data.ccc_interpolator import expand_to_8x8
tm_8x8 = expand_to_8x8(tm_7x7)
"""
import numpy as np
from typing import Optional
# 7x7 index: AAA=0, AA=1, A=2, BBB=3, BB=4, B=5, D=6
# 8x8 index: AAA=0, AA=1, A=2, BBB=3, BB=4, B=5, CCC=6, D=7
GRADES_7 = ["AAA", "AA", "A", "BBB", "BB", "B", "D"]
GRADES_8 = ["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "D"]
def expand_to_8x8(
tm_7x7: np.ndarray,
alpha: float = 0.5,
method: str = "geometric"
) -> np.ndarray:
"""
7x7 transition matrix -> 8x8 with CCC interpolated between B and D.
The CCC row is interpolated from B row and D row.
The CCC column is created by splitting the D column for grades above CCC.
Parameters
----------
tm_7x7 : np.ndarray
7x7 (AAA, AA, A, BBB, BB, B, D) probability matrix
alpha : float
Interpolation weight (0.5 = geometric midpoint between B and D)
method : str
'geometric': log-interpolation (default)
'linear': linear interpolation
Returns
-------
np.ndarray
8x8 (AAA, AA, A, BBB, BB, B, CCC, D) probability matrix
"""
assert tm_7x7.shape == (7, 7), f"Expected (7,7), got {tm_7x7.shape}"
tm_8x8 = np.zeros((8, 8))
# --- Step 1: Copy existing grades (AAA~B) rows/cols ---
# 7x7 index mapping: 0-5 -> 0-5 (AAA~B), 6 -> 7 (D)
for i in range(6): # AAA~B rows
for j in range(6): # AAA~B cols
tm_8x8[i, j] = tm_7x7[i, j]
# D col: 7x7 col6 -> 8x8 col7
tm_8x8[i, 7] = tm_7x7[i, 6]
# --- Step 2: CCC column (col6) for existing grades ---
# For each grade AAA~B, split some probability from D column to CCC
# Rationale: some firms default through CCC before reaching D
for i in range(6):
pd_i = tm_7x7[i, 6] # P(i -> D) in 7x7
if pd_i > 0:
# B row: larger CCC fraction (B is adjacent to CCC)
# Higher grades: smaller CCC fraction
grade_distance_from_b = max(5 - i, 0)
# B->CCC gets ~30%, BB->CCC ~20%, BBB->CCC ~10%, A->CCC ~5%
ccc_fraction = max(0.30 - grade_distance_from_b * 0.06, 0.02)
ccc_prob = pd_i * ccc_fraction
tm_8x8[i, 6] = ccc_prob # to CCC
tm_8x8[i, 7] = pd_i - ccc_prob # remaining to D
else:
tm_8x8[i, 6] = 0.0
# --- Step 3: CCC row (row 6) via interpolation ---
b_row = np.zeros(8)
d_row = np.zeros(8)
# Expand B row (7x7 row5) to 8x8 space
for j in range(6):
b_row[j] = tm_7x7[5, j]
b_row[6] = 0.0 # placeholder for CCC
b_row[7] = tm_7x7[5, 6]
# D row in 8x8: absorbing state
d_row[7] = 1.0
if method == "geometric":
# Geometric interpolation in log space
ccc_row = _geometric_interp(b_row, d_row, alpha)
else:
# Linear interpolation
ccc_row = alpha * b_row + (1 - alpha) * d_row
# Ensure CCC PD is between B PD and 1.0
# CCC should default more than B
ccc_pd = max(ccc_row[7], b_row[7] * 1.5)
ccc_pd = min(ccc_pd, 0.60) # cap at 60%
# CCC stay rate
ccc_stay = max(1.0 - ccc_pd - ccc_row[:6].sum() - ccc_row[6], 0.30)
# Reassemble CCC row
# Upgrade probabilities from B row, scaled down
for j in range(5): # AAA~BB: very small upgrade from CCC
ccc_row[j] = b_row[j] * 0.3 # CCC upgrades less than B
ccc_row[5] = b_row[5] * 0.5 # CCC -> B (upgrade)
ccc_row[6] = ccc_stay # CCC -> CCC (stay)
ccc_row[7] = ccc_pd # CCC -> D
tm_8x8[6, :] = ccc_row
# --- Step 4: D row (absorbing state) ---
tm_8x8[7, :] = 0.0
tm_8x8[7, 7] = 1.0
# --- Step 5: Normalize rows ---
for i in range(8):
s = tm_8x8[i].sum()
if s > 0:
tm_8x8[i] /= s
return tm_8x8
def _geometric_interp(
row_a: np.ndarray,
row_b: np.ndarray,
alpha: float = 0.5,
eps: float = 1e-10
) -> np.ndarray:
"""Geometric (log-space) interpolation between two probability rows."""
result = np.zeros_like(row_a)
for j in range(len(row_a)):
a = max(row_a[j], eps)
b = max(row_b[j], eps)
result[j] = np.exp(alpha * np.log(a) + (1 - alpha) * np.log(b))
return result
def expand_conditional_tm(
cond_7x7: np.ndarray,
ttc_8x8: np.ndarray = None
) -> np.ndarray:
"""
Expand a Z-conditional 7x7 TM to 8x8 using the same interpolation.
This is used in the lifetime PD projection pipeline:
1. Estimate Zt from 7x7 matrices
2. Generate Z-conditional 7x7 TM
3. Expand to 8x8 for lifetime PD calculation
Parameters
----------
cond_7x7 : np.ndarray
Z-conditional 7x7 transition matrix
ttc_8x8 : np.ndarray, optional
Reference TTC 8x8 for CCC structure (if available)
"""
return expand_to_8x8(cond_7x7)
if __name__ == "__main__":
import sys
sys.path.insert(0, ".")
from data.transition_matrices import load_transition_matrices, compute_ttc_matrix
matrices = load_transition_matrices(source="real")
ttc_7x7 = compute_ttc_matrix(matrices)
print("=== TTC 7x7 ===")
for i, g in enumerate(GRADES_7):
print(f" {g:>4}: [{', '.join(f'{v:.4f}' for v in ttc_7x7[i])}]")
ttc_8x8 = expand_to_8x8(ttc_7x7)
print("\n=== TTC 8x8 (CCC interpolated) ===")
for i, g in enumerate(GRADES_8):
print(f" {g:>4}: [{', '.join(f'{v:.4f}' for v in ttc_8x8[i])}]")
# Verify: PD ordering
print("\n=== PD ordering check ===")
for i, g in enumerate(GRADES_8[:-1]):
print(f" {g:>4}: PD = {ttc_8x8[i, -1]*10000:.1f}bp")
# Check row sums
print("\n=== Row sum check ===")
for i in range(8):
print(f" {GRADES_8[i]:>4}: sum = {ttc_8x8[i].sum():.6f}")

View File

@@ -79,6 +79,16 @@ ECOS_VARIABLES = [
# --- 가계 ---
{"name": "HOUSEHOLD_DEBT","stat": "151Y001", "period": "Q", "item": "A11", "agg": "last", "desc": "가계부채 (조원)"},
# --- 투자/저축 (2.1.1.1 주요지표 연간) ---
{"name": "CONSTR_INVEST", "stat": "200Y002", "period": "A", "item": "10315", "agg": None, "desc": "건설투자 증감률 (%)"},
{"name": "GFCF_GROWTH", "stat": "200Y002", "period": "A", "item": "10311", "agg": None, "desc": "총고정자본형성 증감률 (%)"},
{"name": "SAVING_RATE", "stat": "200Y002", "period": "A", "item": "10903", "agg": None, "desc": "총저축률 (%)"},
{"name": "INVEST_RATE", "stat": "200Y002", "period": "A", "item": "10904", "agg": None, "desc": "국내총투자율 (%)"},
{"name": "TRADE_GNI", "stat": "200Y002", "period": "A", "item": "10910", "agg": None, "desc": "수출입의 대 GNI 비율 (%)"},
# --- 제조업 (8.3.7) ---
{"name": "MANUF_CAPACITY", "stat": "901Y033", "period": "M", "item": "I21A", "agg": "mean", "desc": "제조업 평균가동률 (2020=100)"},
]
@@ -98,61 +108,61 @@ ECOS_VARIABLES = [
# SPI: 서비스업생산지수 (2020=100)
def _build_fallback() -> pd.DataFrame:
"""API 없이 작동하는 확장 fallback — 31개 변수"""
"""API 없이 작동하는 확장 fallback — 37개 변수"""
# fmt: off
data = {
2000: {"GDP_GROWTH":8.9,"UNEMPLOYMENT":4.4,"BASE_RATE":5.25,"CD_RATE":7.09,"CPI_GROWTH":2.3,"LEADING_INDEX":101.2,"GOVT_3Y":8.35,"GOVT_10Y":8.55,"CORP_AA":9.35,"CORP_BBB":11.90,"IPI":102.5,"EXPORT":172268,"IMPORT_AMT":160481,"USDKRW":1131,"M2":651.8,"CSI":101.0,"KOSPI":504,"IMPORT_PRICE":78.5,"DISHONOR_RATE":0.46,"HOUSING_PRICE":55.2,"HOUSEHOLD_DEBT":194.0,
"FACILITY_INVEST":62.5,"RETAIL_SALES":72.0,"CURRENT_ACCOUNT":123.5,"EMPLOYED":2115,"EMPLOYMENT_RATE":58.5,"OIL_PRICE":26.2,"COINCIDENT":99.8,"BSI_MANUF":90,"CONSTRUCTION_DONE":56.3,"SPI":58.0},
"FACILITY_INVEST":62.5,"RETAIL_SALES":72.0,"CURRENT_ACCOUNT":123.5,"EMPLOYED":2115,"EMPLOYMENT_RATE":58.5,"OIL_PRICE":26.2,"COINCIDENT":99.8,"BSI_MANUF":90,"CONSTRUCTION_DONE":56.3,"SPI":58.0,"CONSTR_INVEST_GR":-1.4,"GFCF_GROWTH":11.4,"SAVING_RATE":33.7,"INVEST_RATE":31.0,"TRADE_GNI":72.5,"MANUF_CAPACITY":109.5},
2001: {"GDP_GROWTH":4.5,"UNEMPLOYMENT":4.0,"BASE_RATE":4.00,"CD_RATE":5.34,"CPI_GROWTH":4.1,"LEADING_INDEX":99.5,"GOVT_3Y":6.70,"GOVT_10Y":7.05,"CORP_AA":8.12,"CORP_BBB":11.27,"IPI":99.5,"EXPORT":150439,"IMPORT_AMT":141098,"USDKRW":1291,"M2":736.5,"CSI":96.5,"KOSPI":694,"IMPORT_PRICE":73.6,"DISHONOR_RATE":0.28,"HOUSING_PRICE":56.8,"HOUSEHOLD_DEBT":225.0,
"FACILITY_INVEST":58.5,"RETAIL_SALES":73.5,"CURRENT_ACCOUNT":80.3,"EMPLOYED":2118,"EMPLOYMENT_RATE":59.0,"OIL_PRICE":22.8,"COINCIDENT":98.0,"BSI_MANUF":82,"CONSTRUCTION_DONE":53.8,"SPI":60.2},
"FACILITY_INVEST":58.5,"RETAIL_SALES":73.5,"CURRENT_ACCOUNT":80.3,"EMPLOYED":2118,"EMPLOYMENT_RATE":59.0,"OIL_PRICE":22.8,"COINCIDENT":98.0,"BSI_MANUF":82,"CONSTRUCTION_DONE":53.8,"SPI":60.2,"CONSTR_INVEST_GR":5.6,"GFCF_GROWTH":0.6,"SAVING_RATE":31.7,"INVEST_RATE":29.3,"TRADE_GNI":66.3,"MANUF_CAPACITY":105.8},
2002: {"GDP_GROWTH":7.4,"UNEMPLOYMENT":3.3,"BASE_RATE":4.25,"CD_RATE":4.99,"CPI_GROWTH":2.8,"LEADING_INDEX":102.3,"GOVT_3Y":6.06,"GOVT_10Y":6.58,"CORP_AA":7.02,"CORP_BBB":9.75,"IPI":108.5,"EXPORT":162471,"IMPORT_AMT":152126,"USDKRW":1251,"M2":816.3,"CSI":105.0,"KOSPI":628,"IMPORT_PRICE":72.1,"DISHONOR_RATE":0.18,"HOUSING_PRICE":65.3,"HOUSEHOLD_DEBT":306.0,
"FACILITY_INVEST":63.2,"RETAIL_SALES":76.0,"CURRENT_ACCOUNT":53.9,"EMPLOYED":2217,"EMPLOYMENT_RATE":60.0,"OIL_PRICE":23.7,"COINCIDENT":101.5,"BSI_MANUF":92,"CONSTRUCTION_DONE":55.2,"SPI":63.5},
"FACILITY_INVEST":63.2,"RETAIL_SALES":76.0,"CURRENT_ACCOUNT":53.9,"EMPLOYED":2217,"EMPLOYMENT_RATE":60.0,"OIL_PRICE":23.7,"COINCIDENT":101.5,"BSI_MANUF":92,"CONSTRUCTION_DONE":55.2,"SPI":63.5,"CONSTR_INVEST_GR":6.5,"GFCF_GROWTH":6.7,"SAVING_RATE":31.3,"INVEST_RATE":29.1,"TRADE_GNI":62.4,"MANUF_CAPACITY":110.5},
2003: {"GDP_GROWTH":2.9,"UNEMPLOYMENT":3.6,"BASE_RATE":3.75,"CD_RATE":4.24,"CPI_GROWTH":3.5,"LEADING_INDEX":98.8,"GOVT_3Y":4.93,"GOVT_10Y":5.45,"CORP_AA":5.70,"CORP_BBB":8.97,"IPI":109.8,"EXPORT":193817,"IMPORT_AMT":178827,"USDKRW":1192,"M2":879.2,"CSI":96.0,"KOSPI":811,"IMPORT_PRICE":81.3,"DISHONOR_RATE":0.12,"HOUSING_PRICE":71.5,"HOUSEHOLD_DEBT":360.0,
"FACILITY_INVEST":60.5,"RETAIL_SALES":74.0,"CURRENT_ACCOUNT":119.5,"EMPLOYED":2212,"EMPLOYMENT_RATE":59.5,"OIL_PRICE":26.8,"COINCIDENT":99.2,"BSI_MANUF":85,"CONSTRUCTION_DONE":58.0,"SPI":64.8},
"FACILITY_INVEST":60.5,"RETAIL_SALES":74.0,"CURRENT_ACCOUNT":119.5,"EMPLOYED":2212,"EMPLOYMENT_RATE":59.5,"OIL_PRICE":26.8,"COINCIDENT":99.2,"BSI_MANUF":85,"CONSTRUCTION_DONE":58.0,"SPI":64.8,"CONSTR_INVEST_GR":10.0,"GFCF_GROWTH":4.0,"SAVING_RATE":32.6,"INVEST_RATE":30.0,"TRADE_GNI":65.0,"MANUF_CAPACITY":108.2},
2004: {"GDP_GROWTH":4.9,"UNEMPLOYMENT":3.7,"BASE_RATE":3.25,"CD_RATE":3.77,"CPI_GROWTH":3.6,"LEADING_INDEX":100.5,"GOVT_3Y":4.11,"GOVT_10Y":4.73,"CORP_AA":4.72,"CORP_BBB":7.53,"IPI":119.2,"EXPORT":253845,"IMPORT_AMT":224463,"USDKRW":1145,"M2":935.3,"CSI":97.0,"KOSPI":896,"IMPORT_PRICE":90.5,"DISHONOR_RATE":0.08,"HOUSING_PRICE":71.0,"HOUSEHOLD_DEBT":394.0,
"FACILITY_INVEST":66.5,"RETAIL_SALES":74.5,"CURRENT_ACCOUNT":284.2,"EMPLOYED":2272,"EMPLOYMENT_RATE":59.8,"OIL_PRICE":33.5,"COINCIDENT":100.8,"BSI_MANUF":88,"CONSTRUCTION_DONE":63.5,"SPI":66.0},
"FACILITY_INVEST":66.5,"RETAIL_SALES":74.5,"CURRENT_ACCOUNT":284.2,"EMPLOYED":2272,"EMPLOYMENT_RATE":59.8,"OIL_PRICE":33.5,"COINCIDENT":100.8,"BSI_MANUF":88,"CONSTRUCTION_DONE":63.5,"SPI":66.0,"CONSTR_INVEST_GR":1.8,"GFCF_GROWTH":2.1,"SAVING_RATE":34.8,"INVEST_RATE":30.3,"TRADE_GNI":73.5,"MANUF_CAPACITY":113.8},
2005: {"GDP_GROWTH":3.9,"UNEMPLOYMENT":3.7,"BASE_RATE":3.75,"CD_RATE":3.81,"CPI_GROWTH":2.8,"LEADING_INDEX":101.8,"GOVT_3Y":4.27,"GOVT_10Y":4.95,"CORP_AA":4.68,"CORP_BBB":6.51,"IPI":126.0,"EXPORT":284419,"IMPORT_AMT":261238,"USDKRW":1024,"M2":1002.7,"CSI":100.5,"KOSPI":1011,"IMPORT_PRICE":99.2,"DISHONOR_RATE":0.06,"HOUSING_PRICE":73.5,"HOUSEHOLD_DEBT":440.0,
"FACILITY_INVEST":68.0,"RETAIL_SALES":76.5,"CURRENT_ACCOUNT":149.8,"EMPLOYED":2297,"EMPLOYMENT_RATE":60.3,"OIL_PRICE":49.3,"COINCIDENT":101.2,"BSI_MANUF":92,"CONSTRUCTION_DONE":66.0,"SPI":68.5},
"FACILITY_INVEST":68.0,"RETAIL_SALES":76.5,"CURRENT_ACCOUNT":149.8,"EMPLOYED":2297,"EMPLOYMENT_RATE":60.3,"OIL_PRICE":49.3,"COINCIDENT":101.2,"BSI_MANUF":92,"CONSTRUCTION_DONE":66.0,"SPI":68.5,"CONSTR_INVEST_GR":-0.4,"GFCF_GROWTH":1.9,"SAVING_RATE":33.4,"INVEST_RATE":29.7,"TRADE_GNI":72.5,"MANUF_CAPACITY":114.5},
2006: {"GDP_GROWTH":5.2,"UNEMPLOYMENT":3.5,"BASE_RATE":4.50,"CD_RATE":4.72,"CPI_GROWTH":2.2,"LEADING_INDEX":102.5,"GOVT_3Y":4.83,"GOVT_10Y":5.17,"CORP_AA":5.25,"CORP_BBB":7.08,"IPI":136.0,"EXPORT":325465,"IMPORT_AMT":309383,"USDKRW":955,"M2":1089.9,"CSI":106.0,"KOSPI":1434,"IMPORT_PRICE":107.8,"DISHONOR_RATE":0.05,"HOUSING_PRICE":80.2,"HOUSEHOLD_DEBT":497.0,
"FACILITY_INVEST":73.5,"RETAIL_SALES":78.5,"CURRENT_ACCOUNT":53.9,"EMPLOYED":2334,"EMPLOYMENT_RATE":60.9,"OIL_PRICE":61.5,"COINCIDENT":102.8,"BSI_MANUF":95,"CONSTRUCTION_DONE":69.5,"SPI":71.2},
"FACILITY_INVEST":73.5,"RETAIL_SALES":78.5,"CURRENT_ACCOUNT":53.9,"EMPLOYED":2334,"EMPLOYMENT_RATE":60.9,"OIL_PRICE":61.5,"COINCIDENT":102.8,"BSI_MANUF":95,"CONSTRUCTION_DONE":69.5,"SPI":71.2,"CONSTR_INVEST_GR":0.5,"GFCF_GROWTH":3.4,"SAVING_RATE":32.5,"INVEST_RATE":29.6,"TRADE_GNI":73.2,"MANUF_CAPACITY":115.8},
2007: {"GDP_GROWTH":5.5,"UNEMPLOYMENT":3.2,"BASE_RATE":5.00,"CD_RATE":5.36,"CPI_GROWTH":2.5,"LEADING_INDEX":103.1,"GOVT_3Y":5.23,"GOVT_10Y":5.42,"CORP_AA":5.70,"CORP_BBB":7.44,"IPI":144.5,"EXPORT":371489,"IMPORT_AMT":356846,"USDKRW":929,"M2":1181.6,"CSI":108.5,"KOSPI":1897,"IMPORT_PRICE":109.3,"DISHONOR_RATE":0.04,"HOUSING_PRICE":83.5,"HOUSEHOLD_DEBT":560.0,
"FACILITY_INVEST":78.5,"RETAIL_SALES":80.0,"CURRENT_ACCOUNT":59.5,"EMPLOYED":2371,"EMPLOYMENT_RATE":61.3,"OIL_PRICE":68.4,"COINCIDENT":103.5,"BSI_MANUF":97,"CONSTRUCTION_DONE":72.8,"SPI":74.0},
"FACILITY_INVEST":78.5,"RETAIL_SALES":80.0,"CURRENT_ACCOUNT":59.5,"EMPLOYED":2371,"EMPLOYMENT_RATE":61.3,"OIL_PRICE":68.4,"COINCIDENT":103.5,"BSI_MANUF":97,"CONSTRUCTION_DONE":72.8,"SPI":74.0,"CONSTR_INVEST_GR":1.4,"GFCF_GROWTH":4.2,"SAVING_RATE":32.4,"INVEST_RATE":29.4,"TRADE_GNI":77.8,"MANUF_CAPACITY":115.2},
2008: {"GDP_GROWTH":2.8,"UNEMPLOYMENT":3.2,"BASE_RATE":3.00,"CD_RATE":5.70,"CPI_GROWTH":4.7,"LEADING_INDEX":96.5,"GOVT_3Y":5.27,"GOVT_10Y":5.57,"CORP_AA":7.02,"CORP_BBB":10.73,"IPI":148.2,"EXPORT":422007,"IMPORT_AMT":435275,"USDKRW":1103,"M2":1263.2,"CSI":86.0,"KOSPI":1124,"IMPORT_PRICE":132.5,"DISHONOR_RATE":0.11,"HOUSING_PRICE":84.0,"HOUSEHOLD_DEBT":630.0,
"FACILITY_INVEST":76.0,"RETAIL_SALES":79.0,"CURRENT_ACCOUNT":-57.8,"EMPLOYED":2385,"EMPLOYMENT_RATE":61.5,"OIL_PRICE":94.3,"COINCIDENT":98.5,"BSI_MANUF":72,"CONSTRUCTION_DONE":74.5,"SPI":75.5},
"FACILITY_INVEST":76.0,"RETAIL_SALES":79.0,"CURRENT_ACCOUNT":-57.8,"EMPLOYED":2385,"EMPLOYMENT_RATE":61.5,"OIL_PRICE":94.3,"COINCIDENT":98.5,"BSI_MANUF":72,"CONSTRUCTION_DONE":74.5,"SPI":75.5,"CONSTR_INVEST_GR":-2.8,"GFCF_GROWTH":-1.9,"SAVING_RATE":31.5,"INVEST_RATE":31.2,"TRADE_GNI":96.5,"MANUF_CAPACITY":112.8},
2009: {"GDP_GROWTH":0.8,"UNEMPLOYMENT":3.6,"BASE_RATE":2.00,"CD_RATE":2.63,"CPI_GROWTH":2.8,"LEADING_INDEX":98.2,"GOVT_3Y":4.04,"GOVT_10Y":4.85,"CORP_AA":5.80,"CORP_BBB":9.24,"IPI":140.0,"EXPORT":363534,"IMPORT_AMT":323085,"USDKRW":1276,"M2":1404.4,"CSI":85.0,"KOSPI":1683,"IMPORT_PRICE":104.2,"DISHONOR_RATE":0.10,"HOUSING_PRICE":84.8,"HOUSEHOLD_DEBT":694.0,
"FACILITY_INVEST":60.5,"RETAIL_SALES":77.5,"CURRENT_ACCOUNT":328.1,"EMPLOYED":2355,"EMPLOYMENT_RATE":60.1,"OIL_PRICE":61.8,"COINCIDENT":96.5,"BSI_MANUF":68,"CONSTRUCTION_DONE":68.2,"SPI":76.0},
"FACILITY_INVEST":60.5,"RETAIL_SALES":77.5,"CURRENT_ACCOUNT":328.1,"EMPLOYED":2355,"EMPLOYMENT_RATE":60.1,"OIL_PRICE":61.8,"COINCIDENT":96.5,"BSI_MANUF":68,"CONSTRUCTION_DONE":68.2,"SPI":76.0,"CONSTR_INVEST_GR":0.2,"GFCF_GROWTH":-1.0,"SAVING_RATE":31.4,"INVEST_RATE":26.3,"TRADE_GNI":82.0,"MANUF_CAPACITY":102.5},
2010: {"GDP_GROWTH":6.8,"UNEMPLOYMENT":3.7,"BASE_RATE":2.50,"CD_RATE":2.80,"CPI_GROWTH":2.9,"LEADING_INDEX":103.0,"GOVT_3Y":3.72,"GOVT_10Y":4.49,"CORP_AA":4.66,"CORP_BBB":7.98,"IPI":161.5,"EXPORT":466384,"IMPORT_AMT":425212,"USDKRW":1156,"M2":1504.3,"CSI":107.0,"KOSPI":2051,"IMPORT_PRICE":115.8,"DISHONOR_RATE":0.06,"HOUSING_PRICE":87.0,"HOUSEHOLD_DEBT":776.0,
"FACILITY_INVEST":80.5,"RETAIL_SALES":80.5,"CURRENT_ACCOUNT":282.1,"EMPLOYED":2397,"EMPLOYMENT_RATE":60.4,"OIL_PRICE":78.1,"COINCIDENT":103.0,"BSI_MANUF":95,"CONSTRUCTION_DONE":72.0,"SPI":78.5},
"FACILITY_INVEST":80.5,"RETAIL_SALES":80.5,"CURRENT_ACCOUNT":282.1,"EMPLOYED":2397,"EMPLOYMENT_RATE":60.4,"OIL_PRICE":78.1,"COINCIDENT":103.0,"BSI_MANUF":95,"CONSTRUCTION_DONE":72.0,"SPI":78.5,"CONSTR_INVEST_GR":-1.4,"GFCF_GROWTH":5.8,"SAVING_RATE":33.5,"INVEST_RATE":29.5,"TRADE_GNI":87.9,"MANUF_CAPACITY":113.0},
2011: {"GDP_GROWTH":3.7,"UNEMPLOYMENT":3.4,"BASE_RATE":3.25,"CD_RATE":3.55,"CPI_GROWTH":4.0,"LEADING_INDEX":101.2,"GOVT_3Y":3.62,"GOVT_10Y":4.05,"CORP_AA":4.41,"CORP_BBB":7.75,"IPI":168.0,"EXPORT":555214,"IMPORT_AMT":524413,"USDKRW":1108,"M2":1586.5,"CSI":100.0,"KOSPI":1826,"IMPORT_PRICE":130.2,"DISHONOR_RATE":0.05,"HOUSING_PRICE":89.5,"HOUSEHOLD_DEBT":857.0,
"FACILITY_INVEST":82.0,"RETAIL_SALES":82.0,"CURRENT_ACCOUNT":184.1,"EMPLOYED":2424,"EMPLOYMENT_RATE":60.7,"OIL_PRICE":106.0,"COINCIDENT":102.5,"BSI_MANUF":90,"CONSTRUCTION_DONE":73.5,"SPI":80.0},
"FACILITY_INVEST":82.0,"RETAIL_SALES":82.0,"CURRENT_ACCOUNT":184.1,"EMPLOYED":2424,"EMPLOYMENT_RATE":60.7,"OIL_PRICE":106.0,"COINCIDENT":102.5,"BSI_MANUF":90,"CONSTRUCTION_DONE":73.5,"SPI":80.0,"CONSTR_INVEST_GR":-4.9,"GFCF_GROWTH":0.8,"SAVING_RATE":34.0,"INVEST_RATE":29.4,"TRADE_GNI":96.7,"MANUF_CAPACITY":112.5},
2012: {"GDP_GROWTH":2.4,"UNEMPLOYMENT":3.2,"BASE_RATE":2.75,"CD_RATE":3.13,"CPI_GROWTH":2.2,"LEADING_INDEX":100.3,"GOVT_3Y":3.13,"GOVT_10Y":3.35,"CORP_AA":3.76,"CORP_BBB":6.56,"IPI":168.2,"EXPORT":547870,"IMPORT_AMT":519584,"USDKRW":1127,"M2":1673.5,"CSI":100.5,"KOSPI":1997,"IMPORT_PRICE":123.5,"DISHONOR_RATE":0.04,"HOUSING_PRICE":89.0,"HOUSEHOLD_DEBT":934.0,
"FACILITY_INVEST":79.0,"RETAIL_SALES":83.5,"CURRENT_ACCOUNT":508.4,"EMPLOYED":2468,"EMPLOYMENT_RATE":61.3,"OIL_PRICE":109.1,"COINCIDENT":100.5,"BSI_MANUF":85,"CONSTRUCTION_DONE":72.0,"SPI":82.5},
"FACILITY_INVEST":79.0,"RETAIL_SALES":83.5,"CURRENT_ACCOUNT":508.4,"EMPLOYED":2468,"EMPLOYMENT_RATE":61.3,"OIL_PRICE":109.1,"COINCIDENT":100.5,"BSI_MANUF":85,"CONSTRUCTION_DONE":72.0,"SPI":82.5,"CONSTR_INVEST_GR":-3.2,"GFCF_GROWTH":-0.5,"SAVING_RATE":33.8,"INVEST_RATE":28.4,"TRADE_GNI":96.8,"MANUF_CAPACITY":110.2},
2013: {"GDP_GROWTH":3.2,"UNEMPLOYMENT":3.1,"BASE_RATE":2.50,"CD_RATE":2.72,"CPI_GROWTH":1.3,"LEADING_INDEX":100.8,"GOVT_3Y":2.79,"GOVT_10Y":3.28,"CORP_AA":3.19,"CORP_BBB":5.87,"IPI":168.8,"EXPORT":559632,"IMPORT_AMT":515586,"USDKRW":1095,"M2":1756.2,"CSI":103.0,"KOSPI":2011,"IMPORT_PRICE":115.0,"DISHONOR_RATE":0.04,"HOUSING_PRICE":88.8,"HOUSEHOLD_DEBT":980.0,
"FACILITY_INVEST":77.5,"RETAIL_SALES":85.0,"CURRENT_ACCOUNT":812.1,"EMPLOYED":2503,"EMPLOYMENT_RATE":61.6,"OIL_PRICE":105.5,"COINCIDENT":101.0,"BSI_MANUF":88,"CONSTRUCTION_DONE":71.5,"SPI":84.0},
"FACILITY_INVEST":77.5,"RETAIL_SALES":85.0,"CURRENT_ACCOUNT":812.1,"EMPLOYED":2503,"EMPLOYMENT_RATE":61.6,"OIL_PRICE":105.5,"COINCIDENT":101.0,"BSI_MANUF":88,"CONSTRUCTION_DONE":71.5,"SPI":84.0,"CONSTR_INVEST_GR":5.4,"GFCF_GROWTH":3.3,"SAVING_RATE":34.0,"INVEST_RATE":28.7,"TRADE_GNI":93.2,"MANUF_CAPACITY":108.0},
2014: {"GDP_GROWTH":3.2,"UNEMPLOYMENT":3.5,"BASE_RATE":2.00,"CD_RATE":2.36,"CPI_GROWTH":1.3,"LEADING_INDEX":101.0,"GOVT_3Y":2.56,"GOVT_10Y":2.92,"CORP_AA":2.99,"CORP_BBB":5.22,"IPI":168.5,"EXPORT":572665,"IMPORT_AMT":525515,"USDKRW":1053,"M2":1871.0,"CSI":104.0,"KOSPI":1916,"IMPORT_PRICE":105.6,"DISHONOR_RATE":0.04,"HOUSING_PRICE":90.2,"HOUSEHOLD_DEBT":1050.0,
"FACILITY_INVEST":81.0,"RETAIL_SALES":86.5,"CURRENT_ACCOUNT":843.5,"EMPLOYED":2546,"EMPLOYMENT_RATE":62.4,"OIL_PRICE":96.7,"COINCIDENT":101.5,"BSI_MANUF":90,"CONSTRUCTION_DONE":73.8,"SPI":86.0},
"FACILITY_INVEST":81.0,"RETAIL_SALES":86.5,"CURRENT_ACCOUNT":843.5,"EMPLOYED":2546,"EMPLOYMENT_RATE":62.4,"OIL_PRICE":96.7,"COINCIDENT":101.5,"BSI_MANUF":90,"CONSTRUCTION_DONE":73.8,"SPI":86.0,"CONSTR_INVEST_GR":1.1,"GFCF_GROWTH":3.1,"SAVING_RATE":34.5,"INVEST_RATE":29.0,"TRADE_GNI":87.6,"MANUF_CAPACITY":108.8},
2015: {"GDP_GROWTH":2.8,"UNEMPLOYMENT":3.6,"BASE_RATE":1.50,"CD_RATE":1.72,"CPI_GROWTH":0.7,"LEADING_INDEX":100.5,"GOVT_3Y":1.80,"GOVT_10Y":2.25,"CORP_AA":2.18,"CORP_BBB":4.61,"IPI":168.0,"EXPORT":526757,"IMPORT_AMT":436499,"USDKRW":1131,"M2":2010.0,"CSI":103.5,"KOSPI":1961,"IMPORT_PRICE":79.5,"DISHONOR_RATE":0.03,"HOUSING_PRICE":95.0,"HOUSEHOLD_DEBT":1145.0,
"FACILITY_INVEST":84.5,"RETAIL_SALES":88.0,"CURRENT_ACCOUNT":1059.4,"EMPLOYED":2567,"EMPLOYMENT_RATE":62.6,"OIL_PRICE":51.2,"COINCIDENT":101.0,"BSI_MANUF":86,"CONSTRUCTION_DONE":77.5,"SPI":88.5},
"FACILITY_INVEST":84.5,"RETAIL_SALES":88.0,"CURRENT_ACCOUNT":1059.4,"EMPLOYED":2567,"EMPLOYMENT_RATE":62.6,"OIL_PRICE":51.2,"COINCIDENT":101.0,"BSI_MANUF":86,"CONSTRUCTION_DONE":77.5,"SPI":88.5,"CONSTR_INVEST_GR":9.1,"GFCF_GROWTH":5.1,"SAVING_RATE":36.0,"INVEST_RATE":28.8,"TRADE_GNI":79.8,"MANUF_CAPACITY":107.2},
2016: {"GDP_GROWTH":2.9,"UNEMPLOYMENT":3.7,"BASE_RATE":1.25,"CD_RATE":1.48,"CPI_GROWTH":1.0,"LEADING_INDEX":99.8,"GOVT_3Y":1.44,"GOVT_10Y":1.80,"CORP_AA":1.88,"CORP_BBB":4.60,"IPI":168.5,"EXPORT":495426,"IMPORT_AMT":406193,"USDKRW":1161,"M2":2151.1,"CSI":100.0,"KOSPI":2026,"IMPORT_PRICE":78.0,"DISHONOR_RATE":0.03,"HOUSING_PRICE":97.5,"HOUSEHOLD_DEBT":1250.0,
"FACILITY_INVEST":82.0,"RETAIL_SALES":89.5,"CURRENT_ACCOUNT":992.4,"EMPLOYED":2597,"EMPLOYMENT_RATE":63.0,"OIL_PRICE":41.3,"COINCIDENT":100.2,"BSI_MANUF":85,"CONSTRUCTION_DONE":89.5,"SPI":90.0},
"FACILITY_INVEST":82.0,"RETAIL_SALES":89.5,"CURRENT_ACCOUNT":992.4,"EMPLOYED":2597,"EMPLOYMENT_RATE":63.0,"OIL_PRICE":41.3,"COINCIDENT":100.2,"BSI_MANUF":85,"CONSTRUCTION_DONE":89.5,"SPI":90.0,"CONSTR_INVEST_GR":10.3,"GFCF_GROWTH":5.6,"SAVING_RATE":36.4,"INVEST_RATE":29.2,"TRADE_GNI":74.5,"MANUF_CAPACITY":106.0},
2017: {"GDP_GROWTH":3.2,"UNEMPLOYMENT":3.7,"BASE_RATE":1.50,"CD_RATE":1.52,"CPI_GROWTH":1.9,"LEADING_INDEX":101.5,"GOVT_3Y":1.80,"GOVT_10Y":2.33,"CORP_AA":2.28,"CORP_BBB":4.83,"IPI":174.2,"EXPORT":573694,"IMPORT_AMT":478478,"USDKRW":1131,"M2":2347.2,"CSI":105.0,"KOSPI":2467,"IMPORT_PRICE":90.5,"DISHONOR_RATE":0.02,"HOUSING_PRICE":100.0,"HOUSEHOLD_DEBT":1364.0,
"FACILITY_INVEST":92.0,"RETAIL_SALES":92.0,"CURRENT_ACCOUNT":752.6,"EMPLOYED":2620,"EMPLOYMENT_RATE":63.2,"OIL_PRICE":53.1,"COINCIDENT":101.8,"BSI_MANUF":92,"CONSTRUCTION_DONE":90.0,"SPI":92.5},
"FACILITY_INVEST":92.0,"RETAIL_SALES":92.0,"CURRENT_ACCOUNT":752.6,"EMPLOYED":2620,"EMPLOYMENT_RATE":63.2,"OIL_PRICE":53.1,"COINCIDENT":101.8,"BSI_MANUF":92,"CONSTRUCTION_DONE":90.0,"SPI":92.5,"CONSTR_INVEST_GR":7.3,"GFCF_GROWTH":9.8,"SAVING_RATE":36.6,"INVEST_RATE":31.1,"TRADE_GNI":77.3,"MANUF_CAPACITY":107.5},
2018: {"GDP_GROWTH":2.9,"UNEMPLOYMENT":3.8,"BASE_RATE":1.75,"CD_RATE":1.85,"CPI_GROWTH":1.5,"LEADING_INDEX":100.8,"GOVT_3Y":2.10,"GOVT_10Y":2.56,"CORP_AA":2.67,"CORP_BBB":5.41,"IPI":178.0,"EXPORT":604860,"IMPORT_AMT":535202,"USDKRW":1100,"M2":2508.9,"CSI":102.0,"KOSPI":2041,"IMPORT_PRICE":100.0,"DISHONOR_RATE":0.03,"HOUSING_PRICE":102.0,"HOUSEHOLD_DEBT":1497.0,
"FACILITY_INVEST":94.5,"RETAIL_SALES":94.0,"CURRENT_ACCOUNT":774.7,"EMPLOYED":2633,"EMPLOYMENT_RATE":63.1,"OIL_PRICE":69.5,"COINCIDENT":101.5,"BSI_MANUF":88,"CONSTRUCTION_DONE":85.5,"SPI":94.5},
"FACILITY_INVEST":94.5,"RETAIL_SALES":94.0,"CURRENT_ACCOUNT":774.7,"EMPLOYED":2633,"EMPLOYMENT_RATE":63.1,"OIL_PRICE":69.5,"COINCIDENT":101.5,"BSI_MANUF":88,"CONSTRUCTION_DONE":85.5,"SPI":94.5,"CONSTR_INVEST_GR":-4.6,"GFCF_GROWTH":-2.4,"SAVING_RATE":35.9,"INVEST_RATE":30.3,"TRADE_GNI":77.3,"MANUF_CAPACITY":107.0},
2019: {"GDP_GROWTH":2.2,"UNEMPLOYMENT":3.8,"BASE_RATE":1.25,"CD_RATE":1.63,"CPI_GROWTH":0.4,"LEADING_INDEX":99.3,"GOVT_3Y":1.50,"GOVT_10Y":1.74,"CORP_AA":1.93,"CORP_BBB":4.52,"IPI":175.5,"EXPORT":542233,"IMPORT_AMT":503343,"USDKRW":1166,"M2":2694.0,"CSI":97.0,"KOSPI":2198,"IMPORT_PRICE":92.5,"DISHONOR_RATE":0.03,"HOUSING_PRICE":104.5,"HOUSEHOLD_DEBT":1573.0,
"FACILITY_INVEST":89.0,"RETAIL_SALES":96.5,"CURRENT_ACCOUNT":597.0,"EMPLOYED":2660,"EMPLOYMENT_RATE":63.5,"OIL_PRICE":63.4,"COINCIDENT":100.0,"BSI_MANUF":82,"CONSTRUCTION_DONE":82.0,"SPI":97.0},
"FACILITY_INVEST":89.0,"RETAIL_SALES":96.5,"CURRENT_ACCOUNT":597.0,"EMPLOYED":2660,"EMPLOYMENT_RATE":63.5,"OIL_PRICE":63.4,"COINCIDENT":100.0,"BSI_MANUF":82,"CONSTRUCTION_DONE":82.0,"SPI":97.0,"CONSTR_INVEST_GR":-3.1,"GFCF_GROWTH":-2.1,"SAVING_RATE":34.6,"INVEST_RATE":30.5,"TRADE_GNI":72.1,"MANUF_CAPACITY":102.8},
2020: {"GDP_GROWTH":-0.7,"UNEMPLOYMENT":4.0,"BASE_RATE":0.50,"CD_RATE":0.76,"CPI_GROWTH":0.5,"LEADING_INDEX":97.0,"GOVT_3Y":0.98,"GOVT_10Y":1.52,"CORP_AA":2.03,"CORP_BBB":5.25,"IPI":170.0,"EXPORT":512498,"IMPORT_AMT":467633,"USDKRW":1180,"M2":3070.2,"CSI":90.0,"KOSPI":2873,"IMPORT_PRICE":85.0,"DISHONOR_RATE":0.02,"HOUSING_PRICE":110.0,"HOUSEHOLD_DEBT":1723.0,
"FACILITY_INVEST":100.0,"RETAIL_SALES":100.0,"CURRENT_ACCOUNT":752.8,"EMPLOYED":2630,"EMPLOYMENT_RATE":62.5,"OIL_PRICE":42.3,"COINCIDENT":97.5,"BSI_MANUF":76,"CONSTRUCTION_DONE":79.0,"SPI":100.0},
"FACILITY_INVEST":100.0,"RETAIL_SALES":100.0,"CURRENT_ACCOUNT":752.8,"EMPLOYED":2630,"EMPLOYMENT_RATE":62.5,"OIL_PRICE":42.3,"COINCIDENT":97.5,"BSI_MANUF":76,"CONSTRUCTION_DONE":79.0,"SPI":100.0,"CONSTR_INVEST_GR":-0.1,"GFCF_GROWTH":2.6,"SAVING_RATE":36.3,"INVEST_RATE":31.3,"TRADE_GNI":65.8,"MANUF_CAPACITY":100.0},
2021: {"GDP_GROWTH":4.3,"UNEMPLOYMENT":3.7,"BASE_RATE":1.00,"CD_RATE":1.09,"CPI_GROWTH":2.5,"LEADING_INDEX":102.8,"GOVT_3Y":1.43,"GOVT_10Y":2.12,"CORP_AA":2.26,"CORP_BBB":5.64,"IPI":183.0,"EXPORT":644400,"IMPORT_AMT":615093,"USDKRW":1144,"M2":3415.8,"CSI":106.0,"KOSPI":2978,"IMPORT_PRICE":110.5,"DISHONOR_RATE":0.01,"HOUSING_PRICE":122.0,"HOUSEHOLD_DEBT":1853.0,
"FACILITY_INVEST":108.5,"RETAIL_SALES":105.0,"CURRENT_ACCOUNT":883.0,"EMPLOYED":2672,"EMPLOYMENT_RATE":63.8,"OIL_PRICE":69.3,"COINCIDENT":103.0,"BSI_MANUF":96,"CONSTRUCTION_DONE":77.5,"SPI":104.5},
"FACILITY_INVEST":108.5,"RETAIL_SALES":105.0,"CURRENT_ACCOUNT":883.0,"EMPLOYED":2672,"EMPLOYMENT_RATE":63.8,"OIL_PRICE":69.3,"COINCIDENT":103.0,"BSI_MANUF":96,"CONSTRUCTION_DONE":77.5,"SPI":104.5,"CONSTR_INVEST_GR":-1.5,"GFCF_GROWTH":3.1,"SAVING_RATE":35.8,"INVEST_RATE":31.6,"TRADE_GNI":74.5,"MANUF_CAPACITY":105.2},
2022: {"GDP_GROWTH":2.6,"UNEMPLOYMENT":2.9,"BASE_RATE":3.25,"CD_RATE":3.77,"CPI_GROWTH":5.1,"LEADING_INDEX":99.2,"GOVT_3Y":3.14,"GOVT_10Y":3.60,"CORP_AA":4.25,"CORP_BBB":8.18,"IPI":186.5,"EXPORT":683585,"IMPORT_AMT":731370,"USDKRW":1292,"M2":3561.0,"CSI":95.0,"KOSPI":2237,"IMPORT_PRICE":140.2,"DISHONOR_RATE":0.02,"HOUSING_PRICE":128.0,"HOUSEHOLD_DEBT":1903.0,
"FACILITY_INVEST":105.0,"RETAIL_SALES":107.5,"CURRENT_ACCOUNT":258.3,"EMPLOYED":2726,"EMPLOYMENT_RATE":64.5,"OIL_PRICE":97.0,"COINCIDENT":100.5,"BSI_MANUF":85,"CONSTRUCTION_DONE":76.0,"SPI":108.0},
"FACILITY_INVEST":105.0,"RETAIL_SALES":107.5,"CURRENT_ACCOUNT":258.3,"EMPLOYED":2726,"EMPLOYMENT_RATE":64.5,"OIL_PRICE":97.0,"COINCIDENT":100.5,"BSI_MANUF":85,"CONSTRUCTION_DONE":76.0,"SPI":108.0,"CONSTR_INVEST_GR":-3.5,"GFCF_GROWTH":-0.7,"SAVING_RATE":34.5,"INVEST_RATE":31.8,"TRADE_GNI":85.2,"MANUF_CAPACITY":104.5},
2023: {"GDP_GROWTH":1.4,"UNEMPLOYMENT":2.7,"BASE_RATE":3.50,"CD_RATE":3.75,"CPI_GROWTH":3.6,"LEADING_INDEX":98.8,"GOVT_3Y":3.55,"GOVT_10Y":3.78,"CORP_AA":4.40,"CORP_BBB":8.40,"IPI":183.0,"EXPORT":632744,"IMPORT_AMT":642756,"USDKRW":1305,"M2":3680.0,"CSI":96.5,"KOSPI":2655,"IMPORT_PRICE":120.0,"DISHONOR_RATE":0.03,"HOUSING_PRICE":118.0,"HOUSEHOLD_DEBT":1920.0,
"FACILITY_INVEST":102.0,"RETAIL_SALES":106.0,"CURRENT_ACCOUNT":355.2,"EMPLOYED":2750,"EMPLOYMENT_RATE":65.0,"OIL_PRICE":82.5,"COINCIDENT":99.2,"BSI_MANUF":80,"CONSTRUCTION_DONE":72.0,"SPI":109.5},
"FACILITY_INVEST":102.0,"RETAIL_SALES":106.0,"CURRENT_ACCOUNT":355.2,"EMPLOYED":2750,"EMPLOYMENT_RATE":65.0,"OIL_PRICE":82.5,"COINCIDENT":99.2,"BSI_MANUF":80,"CONSTRUCTION_DONE":72.0,"SPI":109.5,"CONSTR_INVEST_GR":-0.5,"GFCF_GROWTH":1.5,"SAVING_RATE":34.0,"INVEST_RATE":30.8,"TRADE_GNI":80.5,"MANUF_CAPACITY":101.0},
2024: {"GDP_GROWTH":2.2,"UNEMPLOYMENT":2.8,"BASE_RATE":3.00,"CD_RATE":3.30,"CPI_GROWTH":2.3,"LEADING_INDEX":99.5,"GOVT_3Y":3.20,"GOVT_10Y":3.42,"CORP_AA":3.90,"CORP_BBB":7.50,"IPI":185.0,"EXPORT":660000,"IMPORT_AMT":650000,"USDKRW":1350,"M2":3800.0,"CSI":98.0,"KOSPI":2400,"IMPORT_PRICE":115.0,"DISHONOR_RATE":0.03,"HOUSING_PRICE":115.0,"HOUSEHOLD_DEBT":1950.0,
"FACILITY_INVEST":103.5,"RETAIL_SALES":105.5,"CURRENT_ACCOUNT":380.0,"EMPLOYED":2760,"EMPLOYMENT_RATE":65.2,"OIL_PRICE":80.0,"COINCIDENT":99.5,"BSI_MANUF":82,"CONSTRUCTION_DONE":68.0,"SPI":110.0},
"FACILITY_INVEST":103.5,"RETAIL_SALES":105.5,"CURRENT_ACCOUNT":380.0,"EMPLOYED":2760,"EMPLOYMENT_RATE":65.2,"OIL_PRICE":80.0,"COINCIDENT":99.5,"BSI_MANUF":82,"CONSTRUCTION_DONE":68.0,"SPI":110.0,"CONSTR_INVEST_GR":-3.3,"GFCF_GROWTH":0.8,"SAVING_RATE":33.5,"INVEST_RATE":30.0,"TRADE_GNI":82.0,"MANUF_CAPACITY":101.5},
2025: {"GDP_GROWTH":1.8,"UNEMPLOYMENT":3.0,"BASE_RATE":2.75,"CD_RATE":3.00,"CPI_GROWTH":1.8,"LEADING_INDEX":99.8,"GOVT_3Y":2.80,"GOVT_10Y":3.10,"CORP_AA":3.50,"CORP_BBB":6.80,"IPI":184.0,"EXPORT":650000,"IMPORT_AMT":640000,"USDKRW":1380,"M2":3900.0,"CSI":99.0,"KOSPI":2500,"IMPORT_PRICE":110.0,"DISHONOR_RATE":0.03,"HOUSING_PRICE":112.0,"HOUSEHOLD_DEBT":1980.0,
"FACILITY_INVEST":104.0,"RETAIL_SALES":106.0,"CURRENT_ACCOUNT":350.0,"EMPLOYED":2770,"EMPLOYMENT_RATE":65.5,"OIL_PRICE":75.0,"COINCIDENT":100.0,"BSI_MANUF":84,"CONSTRUCTION_DONE":65.0,"SPI":111.0},
"FACILITY_INVEST":104.0,"RETAIL_SALES":106.0,"CURRENT_ACCOUNT":350.0,"EMPLOYED":2770,"EMPLOYMENT_RATE":65.5,"OIL_PRICE":75.0,"COINCIDENT":100.0,"BSI_MANUF":84,"CONSTRUCTION_DONE":65.0,"SPI":111.0,"CONSTR_INVEST_GR":-2.0,"GFCF_GROWTH":1.0,"SAVING_RATE":33.0,"INVEST_RATE":29.5,"TRADE_GNI":81.0,"MANUF_CAPACITY":101.0},
}
# fmt: on
return pd.DataFrame(data).T.rename_axis("YEAR")

View File

@@ -103,6 +103,108 @@ def compute_pd_floors(
return pd_floors
# ============================================================
# 기본 PD 플로어 (시장 데이터 없이 사용 가능)
# ============================================================
# 근거:
# AAA = 5bp : Basel III CRE30.4 규제 플로어 (2023 개정, 기업 IRB)
# AA = 5bp : Basel III 최저선 + S&P 장기평균 2bp + Moody's 5bp 중간값
# A = 7bp : S&P 장기평균 5bp + Moody's 9bp 중간값
# BBB = 20bp: S&P 15bp + Moody's 26bp 중간값, 한국 BBB 관측 27bp와 정합
# BB = 60bp: S&P 56~63bp 범위, 관측치 사용 (floor 불필요)
# B = 300bp: S&P 293~334bp 범위, 관측치 사용 (floor 불필요)
#
# 참고문헌:
# [1] Basel Committee, CRE30.4: "PD shall not be less than 0.05%"
# [2] S&P Global, "2023 Annual Default and Transition Study"
# [3] Moody's, "Annual Default Study" (1920-2023)
# [4] 금융감독원, 신용평가공시 (한국기업평가 1998-2025)
DEFAULT_PD_FLOORS = {
"AAA": 0.0005, # 5bp — Basel III CRE30.4
"AA": 0.0005, # 5bp — Basel III 최저선
"A": 0.0007, # 7bp — S&P/Moody's 중간값
"BBB": 0.0020, # 20bp — S&P/Moody's 중간값
"BB": 0.0060, # 60bp — 관측치 수준 (floor 미적용)
"B": 0.0300, # 300bp — 관측치 수준 (floor 미적용)
}
# 7×7 행렬용 (CCC 제외)
GRADES_7 = ["AAA", "AA", "A", "BBB", "BB", "B"]
def get_default_pd_floors() -> Dict[str, float]:
"""기본 PD 플로어 반환 (Basel III + S&P/Moody's 근거)"""
return DEFAULT_PD_FLOORS.copy()
def apply_pd_floor_to_matrices(
matrices: Dict[int, 'np.ndarray'],
pd_floors: Optional[Dict[str, float]] = None,
grades: Optional[List[str]] = None
) -> Dict[int, 'np.ndarray']:
"""
전이행렬의 D열(부도 전이확률)에 PD 플로어 적용
로직:
1. 각 등급의 TM[i, D] < floor[i] 이면
2. TM[i, D] = floor[i] 로 상향
3. 초과분(delta)을 TM[i, i] (대각선)에서 차감
4. 행 합 = 1.0 유지
이론적 근거:
- 한국 투자적격등급(AAA~A) 부도 관측치 = 0%
- 0%"위험 없음"이 아니라 "관측 불가능한 확률"
- Basel III CRE30.4: 기업 PD ≥ 5bp (0.05%)
- S&P/Moody's 글로벌 장기평균으로 보정
Parameters
----------
matrices : Dict[int, np.ndarray]
연도별 전이행렬 (N×N, 마지막 열 = D)
pd_floors : Dict[str, float], optional
등급별 최소 PD (기본: DEFAULT_PD_FLOORS)
grades : List[str], optional
등급 레이블 (기본: AAA~B for 7×7)
Returns
-------
Dict[int, np.ndarray]
PD 플로어 적용된 전이행렬 (새 복사본)
"""
if pd_floors is None:
pd_floors = DEFAULT_PD_FLOORS
if grades is None:
grades = GRADES_7
calibrated = {}
for year, tm in matrices.items():
tm_new = tm.copy()
n = tm_new.shape[0]
d_col = n - 1 # 마지막 열 = D
for i in range(n - 1): # D행은 제외 (흡수상태)
grade = grades[i] if i < len(grades) else None
if grade and grade in pd_floors:
floor = pd_floors[grade]
observed_pd = tm_new[i, d_col]
if observed_pd < floor:
delta = floor - observed_pd
tm_new[i, d_col] = floor
# 대각선(유지확률)에서 차감
tm_new[i, i] = max(tm_new[i, i] - delta, 0.0)
# 행 합 재정규화 (안전장치)
row_sum = tm_new[i].sum()
if row_sum > 0:
tm_new[i] /= row_sum
calibrated[year] = tm_new
logger.info(f"PD 플로어 적용 완료: {len(calibrated)}개 연도")
return calibrated
def extrapolate_speculative_grades(
pd_floors: Dict[str, float],
grades_to_extrapolate: List[str] = ["BB", "B"]

View File

@@ -15,8 +15,12 @@ from pathlib import Path
from typing import Dict, List, Optional, Tuple
# 등급 레이블
RATING_GRADES = ["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "D"]
# Rating grade labels
RATING_GRADES_8 = ["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "D"]
RATING_GRADES_7 = ["AAA", "AA", "A", "BBB", "BB", "B", "D"]
# Default: 7x7 (CCC excluded for Zt estimation)
RATING_GRADES = RATING_GRADES_7
N_GRADES = len(RATING_GRADES)
@@ -205,7 +209,7 @@ def _load_real_matrices(data_dir: Optional[str] = None) -> Dict[int, np.ndarray]
parse_pdf_matrices.py 로 생성된 3사 평균 CSV 사용.
"""
if data_dir is None:
data_dir = str(Path(__file__).parent / "real")
data_dir = str(Path(__file__).parent / "real_v2")
real_dir = Path(data_dir)
if not real_dir.exists():
@@ -320,7 +324,11 @@ def get_default_rates(matrices: Dict[int, np.ndarray]) -> pd.DataFrame:
index=연도, columns=등급, values=연간 PD
"""
years = sorted(matrices.keys())
grades = RATING_GRADES[:-1] # D 제외
n = list(matrices.values())[0].shape[0]
if n == 7:
grades = RATING_GRADES_7[:-1] # AAA~B (D 제외)
else:
grades = RATING_GRADES_8[:-1] # AAA~CCC (D 제외)
data = {}
for year in years:
@@ -332,10 +340,15 @@ def get_default_rates(matrices: Dict[int, np.ndarray]) -> pd.DataFrame:
def display_matrix(tm: np.ndarray, title: str = "전이행렬") -> str:
"""전이행렬을 보기 좋게 포매팅"""
n = tm.shape[0]
if n == 7:
grades = RATING_GRADES_7
else:
grades = RATING_GRADES_8
df = pd.DataFrame(
tm,
index=RATING_GRADES,
columns=RATING_GRADES
index=grades,
columns=grades
)
# 백분율 표시
df_pct = df * 100

View File

@@ -7,4 +7,8 @@
| 3 | 07:42 | ECOS 변수 21→31개 확장 + CSV 캐싱 | `92ce84a`, `49c7661` | ✅ |
| 4 | 07:52 | 31변수 거시경제 재분석 v3 (R²=0.646) | `cc55acc` | ✅ |
| 5 | 08:00 | 파이프라인 전단계 검증 엑셀 생성 (10시트) | `0e1e0e5` | ✅ |
| 6 | 15:50 | 시장 YTM 기반 PD 플로어/WR보정/7×7 전이행렬 전환 설계 및 파서 적용 | TBD | 🔧 |
| 6 | 15:50 | 시장 YTM PD 플로어 + WR보정 + 7x7 전이행렬 파싱 | `b8514c1` | |
| 7 | 16:20 | 7x7 Zt추정 + CCC보간(8x8) + main.py 연결 + 50Y PD검증(8/8통과) + 문서/Wiki | `2b94cc8`~`8a0d6e7` | ✅ |
| 8 | 21:57 | KAP 채권 YTM 기반 PD Floor 실계산 적용 (build_complete_pd_floor_table) | - | ✅ |
| 9 | 22:30 | 확장 변수 탐색 226개 (log/diff/return/lag2), 192만 조합. ADF 수정(AIC->BIC) | - | ✅ |
| 10 | 23:47 | Model #2 선택 (주택가격+신용스프레드+경상수지변화율), 6개 검정 진단표 보고서 | - | ✅ |

View File

@@ -0,0 +1,5 @@
# Devlog: 2026-03-26
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 001 | 19:16~21:28 | Python 환경 세팅(lifetimePD) + 확률 가중평균 시나리오 가중치 실증 분석 스크립트 작성 | `acb232f` | 🔧 |

View File

@@ -0,0 +1,5 @@
# Devlog: 2026-03-27
| # | 시간 | 작업 | 커밋 | 상태 |
|---|------|------|------|------|
| 001 | 17:30~22:40 | ECOS 통계 월별/연간 데이터 정합성 검증 (KOSPI, WTI, 교역조건) | `N/A` | ✅ |

View File

@@ -0,0 +1,43 @@
# 확률 가중평균 시나리오 가중치 고도화 - 방법론 설계 및 실증 분석
- **시간**: 2026-03-26 19:16~21:28
- **Commit**: `acb232f`
- **Vikunja**: 신규 태스크
## 결정 사항
### 1. 확률 가중치 산출 방법론
기존 하드코딩(20/50/30)을 실증 데이터 기반으로 교체하는 방법론을 설계했다.
- **30/50/20 분할 채택**: Downside 30% / Baseline 50% / Upside 20%
- 신용 손실 함수의 비선형성(convexity) 반영 -> 불황에 더 큰 비중
- 분위수 경계: 30th percentile, 80th percentile
- **12개월 롤링 연환산**: 월간 데이터를 연간 베타와 같은 단위로 변환
- 원유(WTI): log(price)
- 순상품교역조건지수: 12개월 수익률
- KOSPI: 12개월 log수익률
- **Virtual Zt 구성**: 연간 회귀 beta를 월별 롤링 데이터에 적용 (300개 표본)
- **정규분포 면적으로 실증 가중치 산출**: 경험적 분위수 경계를 정규분포에 투영
### 2. 검토 후 기각된 방법들
- 3차 가우시안 AND 조건: 확률이 3% 수준으로 너무 작음
- PCA 제1주성분: 변수 상관이 약해(|rho|=0.06~0.18) 설명력 낮음
- 변수별 독립 평균: 단순하여 근거 부족
### 3. 실증 분석 결과 (beta: -0.8037, -10.8856, 3.2878 / const: 2.8575)
- Virtual Zt: mu=0.016, sigma=0.94 (min=-2.54 @ 2009/06, max=+2.21 @ 2005/12)
- 정규분포 면적 가중치: Downside 27.2% / Baseline 56.4% / Upside 16.5%
- Jarque-Bera p=0.085 -> 정규성 기각 못함 -> 정규분포 가정 유효
## 미완료
- tmp_analysis.py를 정식 모듈로 이동/통합 (scenarios/ 또는 projection/)
- 산출된 가중치(27/56/17)를 config.yaml에 반영하고 실제 Lifetime PD 재산출
- 방법론을 docs/methodology.md에 추가 (신규 섹션)
- AGENT.md의 Python 환경이 lifetimePD로 변경됨 -> architecture.md도 동기화 필요

View File

@@ -0,0 +1,15 @@
# ECOS 월별/연간 고시 데이터 정합성 검증
- **시간**: 2026-03-27 17:30~22:40
- **Commit**: `N/A`
- **Vikunja**: N/A
## 결정 사항
- ECOS 월별 데이터를 12개월 평균/기말로 취해 통계청/ECOS 연간 고시 데이터와 완벽 일치할 수 있는지 역산 점검.
- **원유 (WTI)**: 단순 평균으로 소수점 일치 역산 가능.
- **KOSPI 종가**: 기말(12월 말 등) 데이터를 사용하여 소수점 일치 역산 가능.
- **KOSPI 기간평균 / 순상품교역조건지수**: 월별 지수의 12개월 단순 평균값과 연간 고시값 간에 미세 오차(최대 약 1.3포인트, 0.05% 이하) 발생. 월별(M)과 다르게 연별(A) 산출 시 영업일수/교역량 등이 가중치로 작용하기 때문.
- 단, 오차율 1% 미만으로 모델링엔 의미 없으므로 단순 Aggregate로 연간 데이터를 모사하는 데 무리가 없다는 결론을 내림. 부득이하게 심사용 소수점 2째 자리 검증이 필요할 경우만 Annual를 따로 Fetch하기로 가이드.
## 미완료
- 없음

View File

@@ -55,18 +55,24 @@
- 각 기관별 파서 사용 (KR: 텍스트 기반, NICE: 텍스트 기반, SCI: 테이블 기반)
- 추출된 행렬의 행합 유효성 검증 (30~110% 범위)
#### Step 2: WR 열 제거 → 비례 재배분
#### Step 2: WR -> D PD 플로어 보정 + 쟔여 WR 비례 재배분
WR(Withdrawn Rating, 등급취소)은 부도도 유지도 아닌 상태이므로 모형에서 제거해야 합니다.
WR(등급취소)을 단순 제거하면 부도율이 과소추정됩니다(등급취소 직전 부도 위험). 신규 방법론:
1. **PD 플로어 보정**: 관측 PD < 기준 PD이면, WR에서 부족분을 D열로 이전
2. **쟔여 WR 재배분**: 남은 WR을 나머지 등급에 확률 비례 재배분
```
재배분 후 p_{ij} = p_{ij}^{원본} / (1 - p_{i,WR})
if PD_{observed} < PD_{floor}:
deficit = PD_{floor} - PD_{observed}
transfer = min(deficit, WR)
D += transfer
WR -= transfer
# Remaining WR -> proportional redistribution
p_{ij} = p_{ij} * (sum_non_WR + WR_remaining) / sum_non_WR
```
즉, WR을 제외한 나머지 전이확률을 비례적으로 확대하여 행 합 = 1을 복원합니다.
**주의**: WR로 빠진 기업 중 일부는 부도 직전 등급 취소일 수 있어, 부도율이 과소추정될 가능성이 있습니다. 이를 보완하기 위해 Step 5의 PD 플로어를 적용합니다.
#### Step 3: B이하 → B 매핑
3사 원본에서 "B이하" 범주를 모형의 B등급에 매핑합니다.
@@ -82,23 +88,28 @@ D행 = [0, 0, 0, 0, 0, 0, 1]
결과: **7×7 (AAA, AA, A, BBB, BB, B, D)** — 이 행렬로 Zt 추정을 수행합니다.
#### Step 5: PD 플로어 적용
#### Step 5: PD 플로어 적용 (KAP 시장 YTM 기반)
한국 시장에서 AAA~A 등급의 관측 부도율이 0%인 경우가 대부분입니다. 이는 모집단이 작아 극소확률 이벤트가 관측되지 않은 것일 뿐, 부도 위험이 0이라는 의미가 아닙니다.
**Basel III CRE30.4** 및 글로벌 장기 평균에 근거하여 PD 플로어를 적용합니다:
**KAP 시장 YTM 기반 PD 플로어** (2025-12-31 기준):
| 등급 | S&P 장기평균 (1981-2023) | Moody's 장기평균 (1920-2023) | Basel III 플로어 | **적용 PD 플로어** |
|------|:---:|:---:|:---:|:---:|
| AAA | 0bp | 0bp | 5bp | **5bp** |
| AA | 2bp | 5bp | 5bp | **5bp** |
| A | 5bp | 9bp | 5bp | **7bp** |
| BBB | 14~16bp | 26bp | 5bp | **20bp** |
| BB 이하 | ≥56bp | ≥72bp | 5bp | 관측치 사용 |
| 등급 | 신용스프레드 (bp) | 시장내재 PD | AAA 앵커링 후 PD 플로어 | 방법 |
|------|:---:|:---:|:---:|------|
| AAA | 3.5 | 0.6bp | **5.0bp** | 앵커 (Basel III) |
| AA | 5.3 | 0.9bp | **7.0bp** | 로그 스케일링 |
| A | 17.7 | 2.9bp | **15.0bp** | 로그 스케일링 |
| BBB | 129 | 21.5bp | **92.7bp** | 로그 스케일링 |
| BB | - | (외삽) | **160.7bp** | 로그 트렌드 외삽 |
| B | - | (외삽) | **416.4bp** | 로그 트렌드 외삽 |
출처: docs/pd_floor_reference.md 참조
- **데이터 출처**: KAP(한국자산평가) 공모회사채 1년 YTM
- **LGD**: 60% (회수율 40%, 한국 시장 기준)
- **AAA 앵커**: 5bp (Basel III CRE30.4)
- **스케일링**: `PD_floor_i = exp(log(PD_raw_i) * scale)`, `scale = log(5bp) / log(PD_raw_AAA)`
- **투기등급**: BBB- 마지막 관측점에서 로그 트렌드 외삽
관측 PD가 플로어 미만인 경우, 부도열(D) 확률을 플로어 값으로 올리고, 대각 원소(잔류확률)에서 해당 차이만큼 차감하여 행 합 = 1을 유지합니다.
상세 출처: docs/pd_floor_reference.md 참조
#### Step 6: CCC 행/열 보간 → 8×8 완성
@@ -267,141 +278,250 @@ PD_PIT(Z) = Φ( (Φ⁻¹(PD_TTC) - √ρ × Z) / √(1-ρ) ) [Basel: Z>0=불
---
### 2.5 거시연계 회귀모형: Zt ~ 거시변수
### 2.5 AR(1) + Macro 신용사이클 모형
**왜 거시변수와 연결하는가?**
#### 2.5.1 모형의 목적
Zt는 "신용사이클"이라는 추상적 개념입니다. 이를 관측 가능한 거시경제변수로 설명하면:
1. **해석 가능성**: Zt의 변동 원인을 이해할 수 있음
2. **예측 가능성**: 거시 전망치(IMF WEO, KDI 등)를 입력하면 미래 Zt를 예측할 수 있음
3. **시나리오 분석**: "만약 GDP가 -2%이고 실업률이 5%이면?"이라는 질문에 답할 수 있음
Zt는 전이행렬에서 역산한 "신용사이클 인덱스"로, 그 자체로는 **미래를 예측할 수 없습니다.**
IFRS 9 Lifetime PD는 **미래 경기 전망(forward-looking)**을 반영해야 하므로,
관측된 Zt를 **거시경제변수와 연결**하여 미래 Zt 경로를 생성해야 합니다.
**모형 구조:**
이를 위해 **AR(1) + Macro 모형**을 사용합니다. 이 모형은:
1. 신용사이클의 **관성**(φ·Z(t-1))과 **거시경제 충격**(β·X(t))을 동시에 포착
2. **미래 경로 생성**에서 거시변수가 **직접적으로** 기여
3. **Mean-reversion**이 φ에 의해 자동으로 결정 (하드코딩 불필요)
#### 2.5.2 모형 정의
**수학적 구조:**
```
Z_t = β₀ + β₁·GDP_growth_t + β₂·Unemployment_t + β₃·Base_Rate_t
+ β₄·CD_Rate_t + β₅·CPI_growth_t + β₆·Leading_Index_t + ε_t
Z(t) = c + φ·Z(t-1) + β₁·X₁(t) + β₂·X₂(t) + β₃·X₃(t) + ε(t)
```
**변수 선택 (Forward Stepwise, AIC 기준):**
여기서:
- **Z(t)**: 연도 t의 신용사이클 인덱스 (Belkin convention: Z>0 = 호황)
- **Z(t-1)**: 전년도 신용사이클 → **자기회귀(AR) 항**
- **c**: 절편 (장기 균형 수준 조정)
- **φ**: 자기회귀 계수 — **사이클의 관성(persistence)**
- 0 < φ < 1: 정상(stationary) 과정, 자연 감쇠
- φ가 1에 가까울수록 사이클이 오래 지속
- **반감기** = ln(2) / |ln(φ)| 년
- **β₁~β₃**: 거시변수 계수 — 경기 충격의 크기와 방향
- **ε(t) ~ N(0, σ²_ε)**: 잔차 (모형이 설명하지 못하는 변동)
모든 6개 변수를 한꺼번에 넣으면 과적합(overfitting) 위험이 있습니다 (26개 관측치 대비 7개 파라미터).
**장기 균형**:
Forward Stepwise 알고리즘:
1. 빈 모형에서 시작
2. AIC가 가장 많이 감소하는 변수를 하나 추가
3. 더 이상 AIC가 감소하지 않으면 중단
거시 충격이 없고(X=X̄) 충분한 시간이 지나면:
**실제 선택된 변수:** LEADING_INDEX, GDP_GROWTH, UNEMPLOYMENT, CD_RATE (4개)
```
Z∞ = (c + β·X̄) / (1 - φ) ≈ 0 (TTC 수준)
```
**기대 부호:**
#### 2.5.3 변수 선택
| 변수 | 기대 부호 | 근거 |
|------|-----------|------|
| GDP_GROWTH | + | 경기 호황 → Zt 상승 (신용 개선) |
| UNEMPLOYMENT | - | 실업 증가 → Zt 하락 (부도 증가) |
| LEADING_INDEX | + | 경기 선행지수 상승 → Zt 상승 |
| CD_RATE | - | 금리 상승 → 기업 부담 증가 → Zt 하락 |
**변수 풀**: BOK ECOS 100대 통계지표 포함 37개 거시변수
**왜 OLS인가?**
37개 변수에서 3변수 조합 7,770개를 **전수 탐색(exhaustive search)** 하여,
**부호 일관성(sign consistency)**을 만족하는 최적 조합을 선택합니다.
- 26개 연간 관측치로는 VAR, VECM 등 복잡한 시계열 모형의 자유도가 부족
- OLS는 소표본에서도 BLUE(Best Linear Unbiased Estimator) 조건 하에서 최적
- 잔차 진단으로 OLS 가정 위반 여부를 검증
**선택된 3변수:**
| 변수 | 코드 | 계수 부호 | 경제적 근거 |
|------|------|:---------:|------------|
| 원/달러 환율 | USDKRW | | 원화 약세 → 외국인 자본유출, 수입원가 상승 → 기업 부담↑ → Zt↓ |
| 소매판매액지수 | RETAIL_SALES | + | 내수 소비 활성화 → 기업 매출·수익성↑ → Zt↑ |
| 국내총투자율 | INVEST_RATE | + | 투자 확대 → 경기 확장 → 부도 감소 → Zt↑ |
**변수 설계 원칙:**
- 3변수는 각각 **외부충격(환율)**, **내수(소비)**, **투자(자본형성)**를 대표
- **과적합 방지**: 관측치 수 / (변수 수 + AR항) ≈ 25 / 4 = 6.25
- **다중공선성 회피**: 환율·소비·투자는 서로 다른 경기 차원을 포착
#### 2.5.4 왜 AR(1) + Macro 인가?
**기존 OLS 대비 개선:**
| 항목 | OLS 모형 (기존) | AR(1)+Macro (개선) |
|------|---------------|-------------------|
| 미래 Zt 생성 | Zt 분포 통계(μ±kσ) | **φ·Z(t-1) + β·충격** |
| 거시변수 역할 | 사후 해석만 | **시나리오 충격 직접 투영** |
| Mean-reversion | 하드코딩 (λ=0.3) | **φ에서 자동 결정** |
| 사이클 관성 | 무시 | **φ로 포착 (불황 지속성)** |
| IFRS 9 호환 | 약함 | **명시적 forward-looking** |
**이론적 근거:**
- Moody's Analytics: Z-score → macro regression → scenario forecast
- Zanders Group: Vasicek Z → macro regression → PiT 전이행렬
- EBA/ECB: Forward-looking macro overlay on Z-index
- 한국 FSS: 복수 시나리오 + 거시경제 전망 반영 의무
---
### 2.6 통계적 검증 (엄밀한 관점)
### 2.6 시나리오 경로 생성 메커니즘
#### (a) ADF (Augmented Dickey-Fuller) 검정 — Zt 정상성
IFRS 9 (B5.5.42-44)는 **복수의 거시경제 시나리오를 확률 가중**하여
ECL을 산출할 것을 요구합니다. 본 모형에서 시나리오 차이는
**거시변수의 충격(shock) 크기와 방향**에 의해 결정됩니다.
#### 2.6.1 시나리오 정의
각 시나리오는 기준시점(t₀) 대비 **거시변수의 이탈(σ 배수)**로 정의합니다:
| 시나리오 | USDKRW 충격 | RETAIL 충격 | INVEST 충격 | 가중치 |
|---------|:----------:|:----------:|:----------:|:-----:|
| 호황 (Upside) | 1.0σ | +1.0σ | +1.0σ | 20% |
| 중립 (Base) | 0 | 0 | 0 | 50% |
| 불황 (Downside) | +1.5σ | 1.5σ | 1.5σ | 30% |
**σ는 각 변수의 과거 표본 표준편차**입니다.
- σ(USDKRW) ≈ 120원 → Downside 충격 = +180원 (1,380 → 1,560원)
- σ(RETAIL) ≈ 8pt → Downside 충격 = 12pt (107 → 95)
- σ(INVEST) ≈ 1.5%p → Downside 충격 = 2.25%p (30% → 27.75%)
**불황에 더 큰 충격(1.5σ > 1.0σ)을 적용하는 이유:**
1. 신용 손실 함수의 비선형성 — 불황의 PD 증가폭이 호황의 감소폭보다 큼
2. ECB/Fed 감독 관행 — 보수적 추정(conservative estimation) 원칙
3. IFRS 9 B5.5.42: 편향 없는 확률 가중은 테일 리스크를 반영해야 함
#### 2.6.2 Zt 경로 생성 알고리즘
```
H₀: Zt에 단위근이 존재 (비정상 시계열)
H₁: Zt는 정상 시계열
입력: Z(t₀) = Zt의 마지막 관측값
c, φ, β₁, β₂, β₃ = 적합된 AR(1) 파라미터
ΔX = (ΔX₁, ΔX₂, ΔX₃) = 시나리오별 거시 충격
σ_X = 각 변수의 표본 표준편차
```
Zt가 비정상이면 회귀분석의 t-통계량과 R²가 거짓 결과를 낼 수 있습니다 (허위 회귀).
**본 모형 결과: p = 0.0000 → 정상 시계열 확인 (Pass)**
#### (b) Shapiro-Wilk 검정 — Zt 정규성
Belkin & Suchower (1998)는 Z ~ N(0,1)을 가정합니다. 추정된 Zt가 정규분포를 따르는지 확인합니다.
**본 모형 결과: p = 0.0017 → 비정규 (Fail)**
이는 IMF 위기, GFC, COVID 등 극단적 사건으로 인한 비대칭 분포 때문입니다. Belkin 원논문에서도 이 한계를 인정하고 있으며, 실무적으로는 심각한 문제가 아닙니다.
#### (c) Durbin-Watson / Ljung-Box — 잔차 자기상관
**Step 1: 시나리오별 t=1 진입**
```
H₀: 잔차에 자기상관이 없음
DW ≈ 2이면 자기상관 없음
Ljung-Box: p > 0.05이면 자기상관 없음
X_shock(i) = ΔX(i) × σ_X(i)
Z(t₀+1) = c + φ·Z(t₀) + Σᵢ βᵢ·X_shock(i)
```
잔차에 자기상관이 존재하면 OLS 표준오차가 과소추정되어 유의성 검정이 왜곡됩니다.
**본 모형 결과: DW = 2.235, LB p = 0.2743 → 자기상관 없음 (Pass)**
거시변수의 **충격 수준**이 Zt의 초기 분기를 결정합니다.
- Base: X_shock = 0 → Z(t₀+1)은 순수한 AR(1) 감쇠
- Downside: X_shock 반영 → Z(t₀+1)이 음()의 방향으로 이동
- Upside: X_shock 반영 → Z(t₀+1)이 양(+)의 방향으로 이동
#### (d) Breusch-Pagan / ARCH-LM — 이분산
**Step 2: t=2 이후 — 자기회귀 전파 (AR propagation)**
```
H₀: 잔차의 분산이 일정 (등분산)
Z(t₀+k) = c + φ·Z(t₀+k-1) (k ≥ 2)
```
이분산이 존재하면 OLS 추정량은 여전히 불편이지만, 효율적이지 않습니다.
**본 모형 결과: BP p = 0.3951, ARCH p = 0.7885 → 등분산 (Pass)**
t=2부터는 **거시 충격 없이**, φ에 의한 **자연 감쇠**만 적용됩니다.
- φ = 0.7이면: 반감기 ≈ 2.0년 → 충격이 약 4년 만에 10% 이하로 감쇠
- φ = 0.5이면: 반감기 ≈ 1.0년 → 충격이 약 3년 만에 소멸
#### (e) R² / F-test — 모형 설명력
**Step 3: 장기 수렴 (TTC)**
```
R² = 1 - (잔차변동/총변동)
F-test H₀: 모든 회귀계수 = 0
lim_{k→∞} Z(t₀+k) = c / (1 φ) ≈ 0
```
**본 모형 결과: R² = 0.889, F p = 0.0000 → 거시변수가 Zt 변동의 89%를 설명 (Pass)**
충분한 시간이 지나면 모든 시나리오가 **TTC(Z=0)로 자연 수렴**합니다.
이는 경기 사이클이 장기적으로 평균 회귀한다는 가정과 일치합니다.
**핵심: 이 과정에서 거시변수의 미래 값을 예측(forecast)하지 않습니다.**
거시변수는 t=1에서의 **시나리오 진입 충격**만을 정의하며,
t=2 이후는 φ에 의한 내생적(endogenous) 감쇠가 Zt 경로를 결정합니다.
#### 2.6.3 시각화
```
Z(t)
│ ╭── Upside (β·[1σ,+1σ,+1σ] 충격)
│──╱──────── Base (충격 없음, φ 감쇠만)
│ │╲
│ │ ╲
│ │ ╲──── Downside (β·[+1.5σ,1.5σ,1.5σ] 충격)
│ │ ╲
─┼──┼────╲───────────────────→ t
0 t₀ t₀+1 t₀+2 ... t₀+10 ... t₀+50
│←── 충격 ──→│←── φ 감쇠 ──────────→│← TTC (Z≈0) →│
```
---
### 2.7 시나리오 설계 (ECB/Fed 방식)
### 2.7 분기별 업데이트 (Quarterly Refresh)
**IFRS 9 요구사항 (B5.5.42-44):**
#### 2.7.1 연간 Full Calibration vs 분기 Light Update
ECL 산출 시 복수의 시나리오를 확률 가중하여 반영해야 합니다. "편향 없는(unbiased)" 추정을 위해 호황과 불황 양방향을 모두 고려해야 합니다.
| | 연간 (Full) | 분기 (Light) |
|--|-----------|------------|
| **시점** | 연초 (직전년도 데이터 확정 후) | Q2, Q3, Q4 |
| **전이행렬** | 3사 PDF → TTC 재산출 | 변경 없음 |
| **Zt** | 전 기간 WLS 재추정 | 변경 없음 |
| **AR(1) 파라미터** | c, φ, β 재적합 | **변경 없음** (연초 확정값 사용) |
| **거시변수** | 연간 관측값 | **최신 분기/월간 관측값** |
| **Z 경로** | 전체 재생성 | **Z(t₀) 시작점만 갱신** |
| **Lifetime PD** | 전체 재산출 | **갱신된 Z경로로 재산출** |
| 시나리오 | Zt 설정 | 가중치 | 학술적 근거 |
|---------|---------|--------|------------|
| 호황 | μ + 1.0σ | 20% | ECB: 상위 시나리오에 15-25% |
| 중립 | μ + 0σ | 50% | IMF WEO 기본 전망 |
| 불황 | μ - 1.5σ | 30% | Fed DFAST: 역사적 하위 5% |
#### 2.7.2 분기 업데이트 절차
**가중치 비대칭의 이유:**
```
[Step 1] ECOS에서 최신 거시 관측값 수집:
USDKRW(Q_current), RETAIL_SALES(Q_current), INVEST_RATE(Q_current)
불황 시나리오에 더 높은 가중치(30% > 20%)를 부여하는 것은:
1. 신용 손실 함수의 비선형성 — 불황의 영향이 호황의 이익보다 큼
2. ECB/Fed의 감독 관행 — "보수적 추정" 원칙
3. 역사적으로 불황의 빈도가 호황보다 약간 높음
[Step 2] 현재 Z 수준 재계산 (연초 파라미터 사용):
Z_current = c + φ·Z_prev + β₁·USDKRW_Q + β₂·RETAIL_Q + β₃·INVEST_Q
[Step 3] Z_current를 새로운 시작점으로 50년 Z-path 재생성
[Step 4] Vasicek 조건부 TM → 행렬곱 → Lifetime PD 재산출
[Step 5] ECL = Σ EAD(t) × PD(t) × LGD(t) × DF(t) 재산출
```
이 방식의 장점:
- **거시변수가 Lifetime PD에 실시간으로 기여** — IFRS 9 forward-looking 충족
- **모형 재추정 불필요** — 파라미터 안정성 유지
- **감사 추적 가능** — 어떤 거시 관측값이 PD 변동을 야기했는지 추적
---
### 2.8 50년 수렴 메커니즘
### 2.8 IFRS 9 준수 매핑
| IFRS 9 요구사항 | 조항 | 본 모형 대응 |
|---------------|------|-----------|
| Forward-looking 정보 반영 | B5.5.4 | AR(1) macro 충격 → Zt → PiT PD |
| 복수 시나리오 확률 가중 | B5.5.42 | 3 시나리오 × 확률 (20/50/30%) |
| 편향 없는 확률가중 추정 | B5.5.43 | Up/Base/Down 양방향 반영 |
| 과도한 비용·노력 없이 이용 가능 | B5.5.51 | ECOS 공개 데이터 (API) 사용 |
| PiT PD 사용 | 5.5.17 | Vasicek Z-조건부 전이행렬 |
| Lifetime ECL (Stage 2) | 5.5.3 | 50년 누적/한계 PD term structure |
| 정기적 갱신 | B5.5.52 | 분기별 Quarterly Refresh |
**IFRS 9 (2024년 5월 개정, 2026년 1월 발효) 참고:**
최신 개정은 금융자산 분류/측정, ESG 연계, 전자결제 제거에 관한 것이며,
ECL/PD 모형 방법론 자체에는 변경이 없습니다.
(IFRS 17은 보험계약 회계 기준으로, 본 프로젝트(대출/채권 신용위험)와 적용 범위가 다릅니다.)
---
### 2.9 50년 수렴 메커니즘
**왜 수렴이 필요한가?**
거시경제 예측은 현실적으로 3-5년이 한계입니다 (IMF WEO는 5년 전망). 50년 예측에서는:
1. **1~5년 (PIT 구간)**: 거시 시나리오 기반 Zt 적용 — 가장 신뢰도 높은 구간
2. **6~10년 (전환 구간)**: Mean-reversion으로 점진적 수렴 — 불확실성 증가에 대응
3. **11~50년 (TTC 구간)**: Z = 0 (장기 평균) — 경기 사이클이 반복된다는 가정
AR(1) 모형에서 0 < φ < 1이면 Z(t)는 자동으로 장기 균형으로 수렴합니다.
별도의 수렴 메커니즘이 불필요하며, 이것이 AR(1) 모형의 핵심 장점입니다.
**Mean-Reversion 공식:**
**수렴 속도:**
```
Z_t^adj = Z_t^scenario × exp(-λ × (t - T_pit)) (t > T_pit)
```
- λ = 0.3: Mean-reversion 속도 — 5년 후 Z가 약 22%로 감소
- T_pit = 5: PIT 적용 종료 시점
| φ | 반감기 | 충격이 5% 이하로 감쇠 | 50년 시점 잔여 충격 |
|---|--------|-------------------|-----------------|
| 0.3 | 0.6년 | ~2.5년 | ≈ 0% |
| 0.5 | 1.0년 | ~4.3년 | ≈ 0% |
| 0.7 | 1.9년 | ~8.4년 | ≈ 0% |
| 0.9 | 6.6년 | ~28.4년 | ~0.5% |
**학술적 근거:**
- Ornstein-Uhlenbeck 과정: 금리/스프레드 모형에서 널리 사용
- Ornstein-Uhlenbeck 과정의 이산 시간 버전이 AR(1)
- Basel III FRTB: 장기 리스크 파라미터의 평균회귀 가정
- IFRS 9 IG: 예측 불가능한 장기 구간에서는 역사적 평균 사용 권장

51
main.py
View File

@@ -36,8 +36,9 @@ from tabulate import tabulate
# 프로젝트 모듈
from data.transition_matrices import (
load_transition_matrices, compute_ttc_matrix,
get_default_rates, display_matrix, RATING_GRADES
get_default_rates, display_matrix, RATING_GRADES, RATING_GRADES_8
)
from data.ccc_interpolator import expand_to_8x8
from data.macro_data import load_macro_data, _fallback_macro_data, compute_derived_features
from models.credit_cycle import estimate_zt_series, estimate_rho_and_zt
from models.vasicek import conditional_pd, worst_case_pd
@@ -102,18 +103,38 @@ def main():
tm_source = data_config.get("transition_source", "builtin")
tm_dir = data_config.get("transition_dir", None)
logger.info(f"전이행렬 로딩 중 (source={tm_source})...")
transition_matrices = load_transition_matrices(tm_source, data_dir=tm_dir)
transition_matrices_all = load_transition_matrices(tm_source, data_dir=tm_dir)
# 2000-2025 필터
transition_matrices_raw = {y:m for y,m in transition_matrices_all.items() if 2000 <= y <= 2025}
# PD 플로어 적용: KAP 채권 YTM 기반 시장내재 PD
from data.pd_floor import apply_pd_floor_to_matrices, build_complete_pd_floor_table
pd_floors_broad, _, pd_floors_full = build_complete_pd_floor_table()
transition_matrices = apply_pd_floor_to_matrices(transition_matrices_raw, pd_floors_broad)
ttc_matrix = compute_ttc_matrix(transition_matrices)
default_rates = get_default_rates(transition_matrices)
print(f"\n 전이행렬: {len(transition_matrices)}개 연도 ({min(transition_matrices.keys())}~{max(transition_matrices.keys())})"
f" [source={tm_source}]")
print(display_matrix(ttc_matrix, "TTC 전이행렬 (장기 평균)"))
print(f" PD 플로어 (KAP 채권 YTM 기반): AAA={pd_floors_broad['AAA']*10000:.0f}bp, AA={pd_floors_broad['AA']*10000:.0f}bp, "
f"A={pd_floors_broad['A']*10000:.0f}bp, BBB={pd_floors_broad['BBB']*10000:.0f}bp")
print(display_matrix(ttc_matrix, "TTC 전이행렬 (KAP PD Floor 적용 후 장기 평균)"))
# 거시경제변수
if args.no_api:
logger.info("Fallback 거시경제 데이터 사용")
macro_data = _fallback_macro_data()
# ECOS fallback 데이터도 병합 (37개 변수)
try:
from data.ecos_fetcher import load_macro_data as load_ecos_macro
ecos_data = load_ecos_macro()
if ecos_data is not None and not ecos_data.empty:
macro_data = pd.concat([macro_data, ecos_data], axis=1)
macro_data = macro_data.loc[:, ~macro_data.columns.duplicated()]
logger.info(f"ECOS fallback 병합 완료: {len(macro_data.columns)}개 변수")
except Exception as e:
logger.warning(f"ECOS fallback 병합 실패: {e}")
else:
macro_data = load_macro_data(args.config)
@@ -165,9 +186,20 @@ 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_method = config.get("model", {}).get("macro_method", "ar1_macro")
macro_model = build_macro_zt_model(zt_dict, model_input, method=macro_method,
forced_vars=forced_vars)
print(f"\n 선택된 변수: {macro_model.selected_vars}")
if macro_model.is_ar1:
import math
phi = macro_model.ar1_phi
half_life = math.log(2) / abs(math.log(abs(phi))) if 0 < abs(phi) < 1 else float('inf')
print(f" [AR(1)+Macro] φ = {phi:.4f} (반감기 = {half_life:.1f}년)")
print(f" c = {macro_model.ar1_const:.4f}")
for var, beta in macro_model.ar1_beta.items():
print(f" β({var}) = {beta:+.6f}")
print(macro_model.summary())
diag = macro_model.diagnostics()
@@ -227,7 +259,12 @@ def main():
print(" [5/7] 50년 Lifetime PD 산출")
print("=" * 70)
pd_engine = LifetimePDEngine(ttc_matrix, rho)
# 7x7 TTC -> 8x8 TTC (CCC interpolation)
ttc_8x8 = expand_to_8x8(ttc_matrix)
print(f"\n 7x7 TTC -> 8x8 TTC (CCC interpolated)")
print(display_matrix(ttc_8x8, "TTC 8x8 (CCC interpolated)"))
pd_engine = LifetimePDEngine(ttc_8x8, rho, rating_grades=RATING_GRADES_8)
pd_results = pd_engine.compute_all_scenarios(z_paths, weights, horizon)
# 누적 PD 테이블
@@ -244,8 +281,8 @@ def main():
# Vasicek Worst-Case 비교
print("\n === Basel II Worst-Case PD (99.9% VaR) ===")
ttc_pds = ttc_matrix[:-1, -1]
for i, grade in enumerate(RATING_GRADES[:-1]):
ttc_pds = ttc_8x8[:-1, -1]
for i, grade in enumerate(RATING_GRADES_8[:-1]):
wc = worst_case_pd(ttc_pds[i], rho)
print(f" {grade}: TTC={ttc_pds[i]*100:.3f}% → WC={wc*100:.3f}%")

View File

@@ -1,17 +1,16 @@
"""
거시경제 변수 ↔ Zt 연계 통계모형
거시경제 변수 ↔ Zt 연계 AR(1) + Macro 모형
Zt(신용사이클 인덱스)를 거시경제변수로 설명하는 회귀모형을 구축하고,
미래 거시 시나리오에 따른 Zt 전망을 생성합니다.
Zt(신용사이클 인덱스)를 거시경제변수와 자기회귀 항으로 설명하는 모형.
모형:
Z_t = β₀ + β₁·GDP_growth + β₂·Unemployment + β₃·Base_Rate
+ β₄·CD_Rate + β₅·CPI_growth + β₆·Leading_Index + ε_t
AR(1) + Macro 모형:
Z(t) = c + phi*Z(t-1) + beta1*X1(t) + beta2*X2(t) + beta3*X3(t) + eps(t)
방법론 참고:
- IMF (2021). "IFRS 9 and CECL Compatible Estimation for Top-Down Solvency Stress Testing"
- ECB (2019). "Scenario Design for IFRS 9 Expected Credit Loss Estimation"
- Fed (2022). "Dodd-Frank Act Stress Test Methodology"
- Moody's Analytics: Z-score macro regression scenario forecast
- Zanders Group: Vasicek Z macro regression PiT transition matrix
- EBA/ECB: Forward-looking macro overlay on Z-index
- IFRS 9 B5.5.42-44
"""
import numpy as np
@@ -45,14 +44,23 @@ class MacroZtModel:
self.model = None
self.result = None
self.selected_vars = None
self.scaler_params = {} # 정규화 파라미터
self.scaler_params = {}
# AR(1) attributes
self.ar1_phi = None
self.ar1_const = None
self.ar1_beta = None
self.ar1_sigma_eps = None
self.ar1_macro_stats = {}
self.is_ar1 = False
self._ar1_var_names = None
def fit(
self,
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 +106,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"
@@ -245,16 +260,23 @@ class MacroZtModel:
try:
X = self.result.model.exog
vif_values = {}
var_names = ["const"] + self.selected_vars
for i in range(X.shape[1]):
if self.is_ar1 and self._ar1_var_names:
var_names = self._ar1_var_names
else:
var_names = ["const"] + self.selected_vars
for i in range(min(X.shape[1], len(var_names))):
vif_values[var_names[i]] = variance_inflation_factor(X, i)
diag["vif"] = vif_values
except Exception:
diag["vif"] = {}
# 계수 요약
if self.is_ar1 and self._ar1_var_names:
coef_names = self._ar1_var_names
else:
coef_names = ["const"] + self.selected_vars
coef_df = pd.DataFrame({
"변수": ["const"] + self.selected_vars,
"변수": coef_names,
"계수": self.result.params,
"표준오차": self.result.bse,
"t값": self.result.tvalues,
@@ -275,33 +297,201 @@ class MacroZtModel:
if self.result is None:
return np.array([])
return self.result.resid
# ================================================================
# AR(1) + Macro 모형
# ================================================================
def fit_ar1(
self,
zt_series: pd.Series,
macro_data: pd.DataFrame,
forced_vars: Optional[List[str]] = None
) -> "MacroZtModel":
"""
AR(1) + Macro 모형 적합
Z(t) = c + phi*Z(t-1) + beta1*X1(t) + beta2*X2(t) + beta3*X3(t) + eps(t)
Z(t-1)을 설명변수에 포함하여 OLS로 추정합니다.
Parameters
----------
zt_series : pd.Series
index=연도, values=Zt 추정값
macro_data : pd.DataFrame
index=연도, columns=거시변수
forced_vars : List[str], optional
강제 지정 거시변수
"""
self.is_ar1 = True
# 인덱스 정렬 및 교집합
common_years = sorted(set(zt_series.index) & set(macro_data.index))
if len(common_years) < 5:
raise ValueError(f"공통 데이터 포인트가 부족합니다: {len(common_years)}")
# AR(1) 구성: Z(t)와 Z(t-1) 쌍
zt_full = zt_series.loc[common_years].sort_index()
years = list(zt_full.index)
# t-1이 필요하므로 첫 해 제외
y_years = years[1:]
y = zt_full.loc[y_years].values.astype(float)
z_lag = zt_full.loc[years[:-1]].values.astype(float)
# 거시변수
X_macro = macro_data.loc[y_years].copy()
X_macro = X_macro.ffill().bfill().dropna(axis=1)
# 변수 선택
if forced_vars:
available = [v for v in forced_vars if v in X_macro.columns]
if len(available) != len(forced_vars):
missing = set(forced_vars) - set(available)
logger.warning(f"AR(1) 강제 지정 변수 중 누락: {missing}")
self.selected_vars = available
else:
self.selected_vars = list(X_macro.columns)
# 거시변수 표본 통계 저장 (시나리오 충격용)
for col in self.selected_vars:
col_data = macro_data[col].dropna()
self.ar1_macro_stats[col] = {
"mean": float(col_data.mean()),
"std": float(col_data.std()),
"last": float(col_data.iloc[-1]) if len(col_data) > 0 else 0.0
}
# 거시변수 표준화 (mean=0, std=1)
# → β가 "1σ 충격의 Z 영향"으로 직접 해석됨
# → 절편 c가 장기 평균 Z 수준을 결정
X_std = X_macro[self.selected_vars].copy()
for col in self.selected_vars:
mean = self.ar1_macro_stats[col]["mean"]
std = self.ar1_macro_stats[col]["std"]
if std > 0:
X_std[col] = (X_std[col] - mean) / std
else:
X_std[col] = 0.0
# 설계행렬: [const, Z(t-1), X1_std(t), X2_std(t), X3_std(t)]
X_design = np.column_stack([
z_lag,
X_std.values
])
X_with_const = sm.add_constant(X_design)
# OLS 적합
self.model = sm.OLS(y, X_with_const)
self.result = self.model.fit()
# AR(1) 파라미터 추출
# params: [const, phi, beta1, beta2, ...]
params = self.result.params
self.ar1_const = params[0]
self.ar1_phi = params[1]
self.ar1_beta = {}
for i, col in enumerate(self.selected_vars):
self.ar1_beta[col] = params[2 + i]
self.ar1_sigma_eps = float(np.std(self.result.resid))
# 변수명 저장 (진단용)
self._ar1_var_names = ["const", "Z_lag1"] + self.selected_vars
# 정상성 체크
if abs(self.ar1_phi) > 0 and abs(self.ar1_phi) < 1:
half_life = np.log(2) / abs(np.log(abs(self.ar1_phi)))
else:
half_life = np.inf
logger.info(f"AR(1)+Macro 적합 완료:")
logger.info(f" phi = {self.ar1_phi:.4f} (반감기 = {half_life:.1f}년)")
logger.info(f" c = {self.ar1_const:.4f}")
for col, beta in self.ar1_beta.items():
logger.info(f" beta({col}) = {beta:+.6f}")
logger.info(f" R2 = {self.result.rsquared:.4f}, "
f"Adj.R2 = {self.result.rsquared_adj:.4f}")
if abs(self.ar1_phi) >= 1.0:
logger.warning(f" phi={self.ar1_phi:.4f} >= 1.0 -> non-stationary!")
return self
def forecast_z_path(
self,
z_last: float,
macro_shocks: Dict[str, float],
horizon: int = 50
) -> np.ndarray:
"""
AR(1) 모형으로 미래 Z 경로 생성
t=1: Z = c + phi*Z(t0) + sum(beta_i * shock_i * sigma_i)
t>=2: Z = c + phi*Z(t-1) (거시 충격 없음, AR 감쇠만)
Parameters
----------
z_last : float
마지막 관측 Z(t0)
macro_shocks : Dict[str, float]
변수별 충격 (sigma 배수)
예: {"USDKRW": +1.5, "RETAIL_SALES": -1.5}
horizon : int
예측 기간 (년)
Returns
-------
np.ndarray : [Z(t0+1), ..., Z(t0+horizon)]
"""
if not self.is_ar1:
raise ValueError("AR(1) 모형이 적합되지 않았습니다.")
z_path = np.zeros(horizon)
z_prev = z_last
for t in range(horizon):
z_next = self.ar1_const + self.ar1_phi * z_prev
# t=0 (첫 해)만 거시 충격 적용
# β는 표준화된 변수 기준이므로 shock_sigma가 곧 β의 배수
if t == 0:
for var, shock_sigma in macro_shocks.items():
if var in self.ar1_beta:
beta = self.ar1_beta[var]
z_next += beta * shock_sigma
z_path[t] = z_next
z_prev = z_next
return z_path
def build_macro_zt_model(
zt_dict: Dict[int, float],
macro_df: pd.DataFrame,
method: str = "stepwise_aic"
method: str = "ar1_macro",
forced_vars: Optional[List[str]] = None
) -> MacroZtModel:
"""
편의 함수: Zt 딕셔너리 + 거시 DataFrame 회귀모형 구축
편의 함수: Zt + 거시 DataFrame -> 회귀모형 구축
Parameters
----------
zt_dict : Dict[int, float]
{연도: Zt값}
macro_df : pd.DataFrame
index=연도, columns=거시변수
method : str
변수 선택 방법
Returns
-------
MacroZtModel : 적합된 모형
zt_dict : {연도: Zt값}
macro_df : index=연도, columns=거시변수
method : "ar1_macro" (기본) | "stepwise_aic" | "all"
forced_vars : 강제 지정 변수
"""
zt_series = pd.Series(zt_dict, name="Zt")
zt_series.index.name = "YEAR"
model = MacroZtModel()
model.fit(zt_series, macro_df, method=method)
if method == "ar1_macro":
model.fit_ar1(zt_series, macro_df, forced_vars=forced_vars)
else:
model.fit(zt_series, macro_df, method=method, forced_vars=forced_vars)
return model

692
reports/generate_report.py Normal file
View File

@@ -0,0 +1,692 @@
"""
Lifetime PD 분석 보고서 생성기 (Excel)
사용법:
python reports/generate_report.py
python reports/generate_report.py --config config.yaml --output results/report.xlsx
다중 시트 구성:
1. 요약 (Summary) — 모형 개요, 핵심 파라미터, 결론
2. 원시데이터_전이행렬 — 연도별 전이행렬, TTC 행렬
3. 원시데이터_거시변수 — ECOS 거시경제변수 시계열
4. Zt_추정 — Belkin & Suchower Zt 역산 결과
5. AR1_모형 — AR(1)+Macro 회귀 결과, 계수, 진단
6. 시나리오_Z경로 — 3 시나리오별 50년 Zt 경로
7. Lifetime_PD — 시나리오별 누적 PD term structure
8. 가중평균_PD — 확률가중 최종 PD
9. 검증결과 — 통계 검정 결과
"""
import sys, io, os, argparse, math
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
if sys.stdout.encoding != 'utf-8':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
import numpy as np
import pandas as pd
import yaml
from datetime import datetime
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from data.transition_matrices import (
load_transition_matrices, compute_ttc_matrix,
RATING_GRADES, RATING_GRADES_8
)
from data.ccc_interpolator import expand_to_8x8
from data.macro_data import _fallback_macro_data, compute_derived_features
from data.ecos_fetcher import load_macro_data as load_ecos_macro
from models.credit_cycle import estimate_zt_series
from models.macro_model import build_macro_zt_model
from scenarios.scenario_engine import ScenarioEngine
from projection.lifetime_pd import LifetimePDEngine
from validation.statistical_tests import run_full_validation
# ================================================================
# 스타일 정의
# ================================================================
NAVY = "1F3864"
DARK_BLUE = "2B5797"
LIGHT_BLUE = "D6E4F0"
LIGHTER_BLUE = "EDF2F9"
WHITE = "FFFFFF"
BORDER_CLR = "B4C6E7"
TITLE_FONT = Font(name="맑은 고딕", size=16, bold=True, color=WHITE)
HEADER_FONT = Font(name="맑은 고딕", size=10, bold=True, color=WHITE)
SUBHEADER_FONT = Font(name="맑은 고딕", size=10, bold=True, color=NAVY)
BODY_FONT = Font(name="맑은 고딕", size=9)
BODY_BOLD = Font(name="맑은 고딕", size=9, bold=True)
SMALL_FONT = Font(name="맑은 고딕", size=8, color="666666")
NUM_FONT = Font(name="Consolas", size=9)
PASS_FONT = Font(name="맑은 고딕", size=9, bold=True, color="2E7D32")
FAIL_FONT = Font(name="맑은 고딕", size=9, bold=True, color="C62828")
TITLE_FILL = PatternFill("solid", fgColor=NAVY)
HEADER_FILL = PatternFill("solid", fgColor=DARK_BLUE)
SUBHEADER_FILL = PatternFill("solid", fgColor=LIGHT_BLUE)
ALT_FILL = PatternFill("solid", fgColor=LIGHTER_BLUE)
PASS_FILL = PatternFill("solid", fgColor="E2EFDA")
FAIL_FILL = PatternFill("solid", fgColor="FCE4EC")
THIN_BORDER = Border(
left=Side(style='thin', color=BORDER_CLR),
right=Side(style='thin', color=BORDER_CLR),
top=Side(style='thin', color=BORDER_CLR),
bottom=Side(style='thin', color=BORDER_CLR)
)
CENTER = Alignment(horizontal='center', vertical='center', wrap_text=True)
LEFT = Alignment(horizontal='left', vertical='center', wrap_text=True)
RIGHT = Alignment(horizontal='right', vertical='center')
NUM4 = '0.0000'
NUM2 = '0.00'
def _widths(ws, widths):
for i, w in enumerate(widths, 1):
ws.column_dimensions[get_column_letter(i)].width = w
def _title(ws, row, text, ncols=10):
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=ncols)
c = ws.cell(row=row, column=1, value=text)
c.font = TITLE_FONT; c.fill = TITLE_FILL; c.alignment = LEFT
ws.row_dimensions[row].height = 35
def _section(ws, row, text, ncols=10):
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=ncols)
c = ws.cell(row=row, column=1, value=text)
c.font = SUBHEADER_FONT; c.fill = SUBHEADER_FILL; c.alignment = LEFT
ws.row_dimensions[row].height = 22
return row + 1
def _headers(ws, row, hdrs):
for j, h in enumerate(hdrs, 1):
c = ws.cell(row=row, column=j, value=h)
c.font = HEADER_FONT; c.fill = HEADER_FILL; c.alignment = CENTER; c.border = THIN_BORDER
return row + 1
def _row(ws, row, vals, alt=False, fmt=None):
fill = ALT_FILL if alt else PatternFill()
for j, v in enumerate(vals, 1):
c = ws.cell(row=row, column=j, value=v)
c.font = NUM_FONT if isinstance(v, (int, float, np.floating, np.integer)) else BODY_FONT
c.fill = fill; c.border = THIN_BORDER
c.alignment = RIGHT if isinstance(v, (int, float, np.floating, np.integer)) else LEFT
if fmt and isinstance(v, (float, np.floating)):
c.number_format = fmt
return row + 1
def _kv(ws, row, key, value, col=2, fmt=None):
ws.cell(row=row, column=col, value=key).font = BODY_BOLD
cell = ws.cell(row=row, column=col+1, value=value)
cell.font = NUM_FONT if isinstance(value, (int, float, np.floating)) else BODY_FONT
if fmt and isinstance(value, (float, np.floating)):
cell.number_format = fmt
return row + 1
# ================================================================
# 시트 생성
# ================================================================
def sheet_summary(wb, config, model, zt_dict, diag, z_paths, val_df, pd_engine, pd_results, grades):
ws = wb.active; ws.title = "요약"
_widths(ws, [3,25,18,18,18,18,12,12,12,12])
r = 1; _title(ws, r, " Lifetime PD 분석 보고서", 10)
r = 2; ws.cell(row=r, column=2, value=f"생성일시: {datetime.now().strftime('%Y-%m-%d %H:%M')}").font = SMALL_FONT
r = 4
# 1. 모형 개요
r = _section(ws, r, " 1. 모형 개요", 10)
r = _kv(ws, r, "모형 구조", "Z(t) = c + φ·Z(t-1) + β₁·X₁_std + β₂·X₂_std + β₃·X₃_std + ε")
r = _kv(ws, r, "모형 유형", "AR(1) + Macro (Vasicek Single-Factor)")
r = _kv(ws, r, "적용 기준", "IFRS 9 (2018, 2024 개정)")
r = _kv(ws, r, "변수 선택", ", ".join(model.selected_vars))
r += 1
# 2. AR(1) 파라미터
r = _section(ws, r, " 2. AR(1) 모형 파라미터", 10)
r = _kv(ws, r, "자기회귀 계수 (φ)", model.ar1_phi, fmt=NUM4)
phi = model.ar1_phi
hl = math.log(2)/abs(math.log(abs(phi))) if 0<abs(phi)<1 else float('inf')
r = _kv(ws, r, "반감기", f"{hl:.1f}")
r = _kv(ws, r, "절편 (c)", model.ar1_const, fmt=NUM4)
for var, beta in model.ar1_beta.items():
r = _kv(ws, r, f"β({var})", beta, fmt=NUM4)
r = _kv(ws, r, "잔차 σ", model.ar1_sigma_eps, fmt=NUM4)
lr = model.ar1_const / (1 - model.ar1_phi) if abs(model.ar1_phi) < 1 else 0
r = _kv(ws, r, "장기 균형 Z", lr, fmt=NUM4)
r += 1
# 3. 적합도
r = _section(ws, r, " 3. 모형 적합도", 10)
r = _kv(ws, r, "", diag.get("r_squared",0), fmt=NUM4)
r = _kv(ws, r, "Adj. R²", diag.get("adj_r_squared",0), fmt=NUM4)
r = _kv(ws, r, "F p-value", diag.get("f_pvalue",0), fmt=NUM4)
r = _kv(ws, r, "AIC", diag.get("aic",0), fmt=NUM2)
r = _kv(ws, r, "DW", diag.get("durbin_watson",0), fmt=NUM4)
r += 1
# 4. 시나리오
r = _section(ws, r, " 4. 시나리오 설정", 10)
hdrs = ["", "시나리오", "가중치"]
for v in model.selected_vars:
hdrs.append(f"{v} (σ)")
hdrs.append("Z(t+1)")
r = _headers(ws, r, hdrs)
for sname, scfg in config.get("scenarios", {}).items():
vals = [None, scfg.get("name", sname), scfg.get("weight", 0)]
for v in model.selected_vars:
vals.append(scfg.get("macro_shocks", {}).get(v, 0))
z1 = z_paths.get(sname, [0])[0] if z_paths else 0
vals.append(float(z1))
r = _row(ws, r, vals, alt=(sname=="base"), fmt=NUM4)
r += 1
# 5. 1년차 가중 PD
r = _section(ws, r, " 5. 1년차 확률가중 PD (%)", 10)
r = _headers(ws, r, ["", ""] + list(grades[:-1]))
by_sc = pd_results.get("by_scenario", pd_results)
wcpd = pd_results.get("weighted_cumulative_pd", None)
if wcpd is not None and wcpd.shape[0] > 0:
wpd = wcpd[0, :len(grades)-1] * 100
else:
wpd = np.zeros(len(grades)-1)
vals = [None, "가중PD(1Y)"] + list(wpd)
r = _row(ws, r, vals, fmt=NUM4)
def sheet_tm(wb, tm_raw, tm_floor, ttc, pd_floors, config):
ws = wb.create_sheet("원시데이터_전이행렬")
grades = config.get("model",{}).get("rating_grades", RATING_GRADES)
ng = len(grades)
_widths(ws, [3,12]+[12]*ng)
nc = 2+ng; r=1
_title(ws, r, " 전이행렬 파이프라인: Original → PD Floor → TTC", nc)
r=3
# KAP 채권 YTM 기반 PD Floor 산출 과정
from data.ytm_fetcher import get_ytm_data, compute_spreads, compute_broad_grade_spreads
from data.pd_floor import compute_market_implied_pd
ytm_data = get_ytm_data()
notch_spreads = compute_spreads(ytm_data)
broad_spreads = compute_broad_grade_spreads(notch_spreads)
lgd = 0.60
rf = ytm_data.get('rf', 0)
r = _section(ws, r, " KAP 채권 YTM → 신용스프레드 → 시장내재 PD (Floor 산출 근거)", nc)
hdr_ytm = ["", "등급", "KAP YTM(%)", "스프레드(bp)", "내재PD(bp)", "Basel III(bp)", "적용Floor(bp)"]
while len(hdr_ytm) < 2 + ng:
hdr_ytm.append("")
r = _headers(ws, r, hdr_ytm[:2 + ng])
floor_grades = ["AAA", "AA", "A", "BBB", "BB", "B"]
for fg in floor_grades:
ytm_val = None
for notch in [fg, fg + '+', fg + '-']:
if notch in ytm_data:
ytm_val = ytm_data[notch]
break
if ytm_val is None:
ytm_val = rf
sp = broad_spreads.get(fg, 0)
implied_pd = compute_market_implied_pd(sp, lgd) * 10000
applied = pd_floors.get(fg, 0) * 10000
v = [None, fg, ytm_val, sp, implied_pd, 5, applied]
while len(v) < 2 + ng:
v.append(None)
r = _row(ws, r, v[:2 + ng], fmt=NUM2)
ws.cell(row=r, column=2,
value=f"기준일: 2025-12-31, 국고1Y: {rf}%, LGD: 60%, 출처: KAP(한국자산평가)").font = SMALL_FONT
r += 1
ws.cell(row=r, column=2,
value="산식: Implied PD = 1 - exp(-spread_bp / (LGD×10000)), Floor = max(Implied PD, Basel 5bp)").font = SMALL_FONT
r += 2
# TTC 전이행렬
r = _section(ws, r, f" TTC 전이행렬 (PD Floor 적용 후, {min(tm_floor.keys())}~{max(tm_floor.keys())} 평균)", nc)
r = _headers(ws, r, ["","From\\To"]+grades)
for i,g in enumerate(grades):
vals = [None,g]+[ttc[i,j] for j in range(min(ng,ttc.shape[1]))]
r = _row(ws, r, vals, alt=i%2==1, fmt=NUM4)
r += 1
# 전체 연도별 전이행렬 (Floor 적용 후)
for year in sorted(tm_floor.keys()):
r = _section(ws, r, f" {year}년 전이행렬 (PD Floor 적용 후)", nc)
r = _headers(ws, r, ["","From\\To"]+grades)
mat = tm_floor[year]
for i,g in enumerate(grades):
if i < mat.shape[0]:
vals = [None,g]+[mat[i,j] for j in range(min(ng,mat.shape[1]))]
r = _row(ws, r, vals, alt=i%2==1, fmt=NUM4)
r += 1
# 영문→한글 변수명 매핑
VAR_KOR = {
"GDP_GROWTH": "GDP성장률(%)", "IPI": "광공업생산지수", "SPI": "서비스업생산지수",
"MANUF_CAPACITY": "제조업가동률", "GFCF_GROWTH": "총고정자본증감률",
"CONSTR_INVEST": "건설투자증감률", "FACILITY_INVEST": "설비투자지수",
"RETAIL_SALES": "소매판매액지수", "CSI": "소비자심리지수", "BSI_MANUF": "제조업BSI",
"LEADING_INDEX": "경기선행지수", "COINCIDENT": "경기동행지수",
"EXPORT": "수출(백만달러)", "IMPORT_AMT": "수입(백만달러)",
"TRADE_GNI": "수출입/GNI(%)", "KOSPI": "KOSPI지수",
"INVEST_RATE": "국내총투자율(%)", "SAVING_RATE": "총저축률(%)",
"HOUSING_PRICE": "주택매매가격지수",
"UNEMPLOYMENT": "실업률(%)", "EMPLOYMENT": "고용률(%)",
"EMPLOYED": "취업자수(만명)", "EMPLOYMENT_RATE": "고용률(%)",
"BASE_RATE": "기준금리(%)", "CD_RATE": "CD91일(%)",
"GOVT_3Y": "국고3Y(%)", "GOVT_10Y": "국고10Y(%)",
"CORP_AA": "회사체AA-(%)", "CORP_BBB": "회사체BBB-(%)",
"CPI_GROWTH": "소비자물가상승률(%)", "IMPORT_PRICE": "수입물가지수",
"PPI": "생산자물가지수", "USDKRW": "원/달러환율",
"M2": "M2광의통화(조원)", "DISHONOR_RATE": "어음부도율(%)",
"DISHONOR_AMT": "부도금액(억원)", "HOUSEHOLD_DEBT": "가계부채(조원)",
"CONSTRUCTION": "건설수주액(억원)", "CONSTRUCTION_DONE": "건설기성액",
"CREDIT_SPREAD": "신용스프레드(BBB-AA)", "TERM_SPREAD": "기간스프레드(10Y-3Y)",
"CREDIT_SPREAD_LAG1": "신용스프레드(t-1)",
"EXPORT_DIFF": "수출증감액", "IPI_LAG1": "광공업생산(t-1)",
"CONSTR_INVEST_GR": "건설투자증가율", "CURRENT_ACCOUNT": "경상수지",
}
# 변환 변수 한글명 자동 생성
TRANSFORM_SUFFIX = {"_LAG2": "(t-2)", "_L": "(log)", "_D": "(차분)",
"_R": "(수익률)", "_LR": "(log수익률)"}
def _kor(varname):
if varname in VAR_KOR:
return VAR_KOR[varname]
for sfx, label in TRANSFORM_SUFFIX.items():
if varname.endswith(sfx):
base = varname[:-len(sfx)]
base_kor = VAR_KOR.get(base, base)
return f"{base_kor}{label}"
return varname
def sheet_macro(wb, macro_data, forced_vars):
ws = wb.create_sheet("원시데이터_거시변수")
display_cols = list(forced_vars) + [c for c in macro_data.columns if c not in forced_vars]
display_cols = [c for c in display_cols if c in macro_data.columns]
_widths(ws, [3,8]+[14]*len(display_cols))
nc = 2+len(display_cols); r=1
_title(ws, r, " 원시 데이터: 거시경제변수", nc)
r=3
r = _section(ws, r, f" ★ 선택 변수: {', '.join([_kor(v) for v in forced_vars])}", nc)
r = _headers(ws, r, ["","연도"]+[_kor(c) for c in display_cols])
for i,(year,rd) in enumerate(macro_data.iterrows()):
vals = [None,int(year)]+[rd[c] if c in rd and pd.notna(rd[c]) else None for c in display_cols]
r = _row(ws, r, vals, alt=i%2==1, fmt=NUM2)
def sheet_zt(wb, zt_dict, macro_data, forced_vars, rho):
ws = wb.create_sheet("Zt_추정")
ncols = 3+len(forced_vars)
_widths(ws, [3,10,14]+[14]*len(forced_vars))
r=1; _title(ws, r, " Zt 추정 (Belkin & Suchower 1998)", ncols)
r=3
r = _section(ws, r, " 방법론: 관측 전이행렬 역산 → WLS → Zt", ncols)
zv = np.array(list(zt_dict.values()))
r = _kv(ws, r, "자산상관계수 (ρ)", rho, fmt=NUM4)
r += 1
# ρ 근거
r = _section(ws, r, " ρ = 0.20 근거", ncols)
ws.cell(row=r, column=2, value="[1] Basel III IRB: 기업 ρ = 0.12~0.24 (CRE31.6)").font = SMALL_FONT; r+=1
ws.cell(row=r, column=2, value=" R = 0.12×(1-e^(-50×PD))/(1-e^(-50)) + 0.24×(1-(1-e^(-50×PD))/(1-e^(-50)))").font = SMALL_FONT; r+=1
ws.cell(row=r, column=2, value="[2] BBB(PD≈0.2%) → R=0.208, A(PD≈0.07%) → R=0.217").font = SMALL_FONT; r+=1
ws.cell(row=r, column=2, value="[3] 한국 기업 포트폴리오 평균: ρ ≈ 0.20 (투자/투기 혼합)").font = SMALL_FONT; r+=1
ws.cell(row=r, column=2, value="[4] Moody's Analytics CreditEdge: single-factor ρ ≈ 0.15~0.25").font = SMALL_FONT; r+=1
r += 1
r = _kv(ws, r, "Zt 평균 (μ)", float(zv.mean()), fmt=NUM4)
r = _kv(ws, r, "Zt 표준편차 (σ)", float(zv.std()), fmt=NUM4)
r = _kv(ws, r, "관측 기간", f"{min(zt_dict.keys())}~{max(zt_dict.keys())} ({len(zt_dict)}개년)")
r += 1
hdrs = ["","연도","Zt"]+forced_vars
r = _headers(ws, r, hdrs)
for i,(year,zt) in enumerate(sorted(zt_dict.items())):
vals = [None,int(year),float(zt)]
for v in forced_vars:
if v in macro_data.columns and year in macro_data.index:
vals.append(macro_data.loc[year,v] if pd.notna(macro_data.loc[year,v]) else None)
else: vals.append(None)
r = _row(ws, r, vals, alt=i%2==1, fmt=NUM4)
def sheet_ar1(wb, model, diag):
ws = wb.create_sheet("AR1_모형")
_widths(ws, [3,22,14,14,14,14,14])
r=1; _title(ws, r, " AR(1) + Macro 회귀 모형", 7)
r=3
r = _section(ws, r, " Z(t) = c + φ·Z(t-1) + Σ βᵢ·Xᵢ_std(t) + ε(t)", 7)
ws.cell(row=r, column=2, value="※ 거시변수는 표준화(mean=0, std=1) 후 투입. β = '1σ 충격 → ΔZ'로 해석").font = SMALL_FONT
r += 2
# 계수
r = _section(ws, r, " 회귀 계수", 7)
r = _headers(ws, r, ["","변수","계수","표준오차","t값","p값","유의성"])
coef_df = diag.get("coefficients", pd.DataFrame())
for i,(_,rd) in enumerate(coef_df.iterrows()):
pv = rd.get("p값",1)
sig = "***" if pv<0.01 else "**" if pv<0.05 else "*" if pv<0.10 else ""
vals = [None, rd.get("변수",""), rd.get("계수",0), rd.get("표준오차",0), rd.get("t값",0), pv, sig]
rn = _row(ws, r, vals, alt=i%2==1, fmt=NUM4)
if pv < 0.05: ws.cell(row=r,column=7).font = PASS_FONT
elif pv < 0.10: ws.cell(row=r,column=7).font = Font(name="맑은 고딕",size=9,color="FF8F00")
r = rn
r += 1
# 진단 — 모형 적합도
r = _section(ws, r, " 모형 적합도", 7)
for k,v,f in [("","r_squared",NUM4),("Adj. R²","adj_r_squared",NUM4),
("F 통계량","f_stat",NUM4),("F p-value","f_pvalue",NUM4),
("AIC","aic",NUM2),("BIC","bic",NUM2)]:
r = _kv(ws, r, k, diag.get(v, None), fmt=f)
r += 1
# 진단 — 잔차 검정 (6개 전항목)
r = _section(ws, r, " 잔차 검정 (6개 전항목)", 7)
r = _headers(ws, r, ["","검정","통계량","p-value","기준","결과","해석"])
tests_data = [
("ADF (Zt 정상성)", diag.get("adf_stat"), diag.get("adf_pvalue"),
"p < 0.05", diag.get("adf_pvalue",1) < 0.05 if diag.get("adf_pvalue") else False,
"BIC lag 선택, H0: 비정상"),
("Ljung-Box Q(5)", diag.get("ljung_box_stat"), diag.get("ljung_box_pvalue"),
"p > 0.05", diag.get("ljung_box_pvalue",0) > 0.05 if diag.get("ljung_box_pvalue") else False,
"H0: 자기상관 없음"),
("Durbin-Watson", diag.get("durbin_watson"), None,
"1.5~2.5", 1.5 <= diag.get("durbin_watson",0) <= 2.5 if diag.get("durbin_watson") else False,
"≈2 이상적"),
("Breusch-Pagan", diag.get("bp_stat"), diag.get("bp_pvalue"),
"p > 0.05", diag.get("bp_pvalue",0) > 0.05 if diag.get("bp_pvalue") else False,
"H0: 등분산"),
("ARCH-LM", diag.get("arch_stat"), diag.get("arch_pvalue"),
"p > 0.05", diag.get("arch_pvalue",0) > 0.05 if diag.get("arch_pvalue") else False,
"H0: ARCH 효과 없음"),
("Shapiro-Wilk", diag.get("shapiro_stat"), diag.get("shapiro_pvalue"),
"p > 0.05", diag.get("shapiro_pvalue",0) > 0.05 if diag.get("shapiro_pvalue") else False,
"H0: 정규분포"),
]
for tname, stat, pval, crit, passed, note in tests_data:
stat_str = f"{stat:.4f}" if stat is not None else "-"
pval_str = f"{pval:.4f}" if pval is not None else "-"
result_str = "Pass ✅" if passed else "Fail ❌"
vals = [None, tname, stat_str, pval_str, crit, result_str, note]
r = _row(ws, r, vals)
if passed:
ws.cell(row=r-1, column=6).font = PASS_FONT
else:
ws.cell(row=r-1, column=6).font = FAIL_FONT
r += 1
# 변수 통계
r = _section(ws, r, " 거시변수 표본 통계 (표준화 전 원시값)", 7)
r = _headers(ws, r, ["","변수","평균","표준편차","최근값","",""])
for var,st in model.ar1_macro_stats.items():
vals = [None,_kor(var),st["mean"],st["std"],st["last"],None,None]
r = _row(ws, r, vals, fmt=NUM2)
r += 1
# 경제적 해석 섹션
r = _section(ws, r, " 변수별 경제적 해석", 7)
interp = {
"CORP_BBB_LAG2": "2년전 BBB금리↑ → 신용위험 잔존 → 부도↑ → Z↓ (시차효과)",
"GFCF_GROWTH_LAG2": "2년전 고정자본투자↑ → 생산능력↑ → 부도↓ → Z↑",
"SAVING_RATE_L": "log(저축률)↑ → 경제안정성↑ → 부도↓ → Z↑",
"HOUSING_PRICE": "주택가격↑ → 담보가치↑ → 차입여력↑ → 부도↓ → Z↑",
"CREDIT_SPREAD_LAG1": "전년 스프레드↑ → 당해 신용위험 전이 → 부도↑ → Z↓ (시차 효과)",
"EXPORT_DIFF": "수출증감↑ → 기업매출↑ → 수익성↑ → 부도↓ → Z↑",
"CURRENT_ACCOUNT": "경상수지↑(흑자) → 불황기 수출의존↑ → Z↓",
"CURRENT_ACCOUNT_R": "경상수지변화율↑ → 대외부문 개선 속도↑ → Z↑ (단기 모멘텀)",
"LEADING_INDEX": "경기선행지수↑ → 3~6개월 후 경기확장 → 부도↓ → Z↑",
"CONSTR_INVEST_GR": "건설투자↑ → 과잉투자/레버리지 → Z↓ (민스키 가설)",
}
for var in model.selected_vars:
beta = model.ar1_beta.get(var, 0)
sign = "+" if beta > 0 else ""
desc = interp.get(var, "")
ws.cell(row=r, column=2, value=_kor(var)).font = BODY_BOLD
ws.cell(row=r, column=3, value=f"β={beta:+.4f} ({sign})").font = NUM_FONT
ws.cell(row=r, column=4, value=desc).font = SMALL_FONT
ws.merge_cells(start_row=r, start_column=4, end_row=r, end_column=7)
r += 1
def sheet_zpath(wb, z_paths, config):
ws = wb.create_sheet("시나리오_Z경로")
scenarios = list(z_paths.keys())
nc = 2+len(scenarios)
_widths(ws, [3,10]+[16]*len(scenarios))
r=1; _title(ws, r, " 시나리오별 Z(t) 경로", nc)
r=3
r = _section(ws, r, " t=1: 거시 충격 적용 | t≥2: AR(1) 감쇠 → TTC 수렴", nc)
r += 1
names = []
for s in scenarios:
c = config.get("scenarios",{}).get(s,{})
names.append(c.get("name",s))
r = _headers(ws, r, ["","연도(t+k)"]+names)
horizon = len(list(z_paths.values())[0])
key_years = list(range(1,11))+[15,20,25,30,40,50]
for t in key_years:
if t <= horizon:
vals = [None,t]+[float(z_paths[s][t-1]) for s in scenarios]
r = _row(ws, r, vals, alt=t%2==0, fmt=NUM4)
def sheet_pd(wb, pd_results, config, grades8):
ws = wb.create_sheet("Lifetime_PD")
ng = len(grades8)-1 # D 제외
_widths(ws, [3,14,8]+[14]*ng)
nc = 3+ng
r=1; _title(ws, r, " 시나리오별 누적 Lifetime PD (%)", nc)
r=3
ky = [1,2,3,5,7,10,15,20,30,50]
by_sc = pd_results.get("by_scenario", {})
for sname, sdata in by_sc.items():
c = config.get("scenarios",{}).get(sname,{})
dn = c.get("name",sname); w = c.get("weight",0)
r = _section(ws, r, f" {dn} (가중치 {w*100:.0f}%)", nc)
r = _headers(ws, r, ["","시나리오","연도"]+list(grades8[:-1]))
cpd = sdata.get("cumulative_pd", np.zeros((50,ng)))
for t in ky:
if t <= cpd.shape[0]:
vals = [None,dn,t]+[cpd[t-1,g]*100 for g in range(min(ng,cpd.shape[1]))]
r = _row(ws, r, vals, alt=ky.index(t)%2==1, fmt=NUM4)
r += 1
def sheet_weighted(wb, pd_results, config, grades8):
ws = wb.create_sheet("가중평균_PD")
ng = len(grades8)-1
_widths(ws, [3,8]+[14]*ng)
nc = 2+ng
r=1; _title(ws, r, " 확률가중 Lifetime PD (%)", nc)
r=3
# IFRS 9 근거
r = _section(ws, r, " IFRS 9 근거: 확률가중 기대신용손실", nc)
ws.cell(row=r, column=2, value='IFRS 9 B5.5.42: "기대신용손실은 확률가중 금액이어야 하며,').font = SMALL_FONT; r+=1
ws.cell(row=r, column=2, value='가능한 결과의 범위를 반영하여야 한다. 단일 가장 가능성 높은 결과가 아닌,').font = SMALL_FONT; r+=1
ws.cell(row=r, column=2, value='신용위험의 벽혹을 변경시키는 일반적 경제 조건에 대한 예측을 포함하여야 한다."').font = SMALL_FONT; r+=1
ws.cell(row=r, column=2, value='IFRS 9 B5.5.44: "최소 2개 시나리오(호황/불황)+확률가중치 = ECL 요구사항을 충족할 수 있다."').font = SMALL_FONT; r+=1
r += 1
r = _section(ws, r, " PD_weighted(t) = Σ w_s × PD_s(t)", nc)
wstr = " + ".join([f"{c.get('weight',0)*100:.0f}%×{c.get('name',s)}" for s,c in config.get("scenarios",{}).items()])
ws.cell(row=r, column=2, value=f"= {wstr}").font = SMALL_FONT
r += 2
ky = [1,2,3,5,7,10,15,20,30,50]
r = _headers(ws, r, ["","연도"]+list(grades8[:-1]))
wcpd = pd_results.get("weighted_cumulative_pd", np.zeros((50, ng)))
for t in ky:
if t <= wcpd.shape[0]:
wpd = wcpd[t-1,:ng] * 100
else:
wpd = np.zeros(ng)
vals = [None,t]+list(wpd)
r = _row(ws, r, vals, alt=ky.index(t)%2==1, fmt=NUM4)
def sheet_validation(wb, val_df):
ws = wb.create_sheet("검증결과")
_widths(ws, [3,30,22,14,14,10,40])
r=1; _title(ws, r, " 통계적 검증 결과", 7)
r=3
cols = list(val_df.columns)
r = _headers(ws, r, [""]+cols)
for i,(_,rd) in enumerate(val_df.iterrows()):
vals = [None]+[rd[c] for c in cols]
rn = _row(ws, r, vals, alt=i%2==1)
# 결과 색상
result_col = cols.index("결과")+2 if "결과" in cols else None
if result_col:
cell = ws.cell(row=r, column=result_col)
if "Pass" in str(cell.value):
cell.fill = PASS_FILL; cell.font = PASS_FONT
elif "Fail" in str(cell.value):
cell.fill = FAIL_FILL; cell.font = FAIL_FONT
r = rn
# ================================================================
# 메인
# ================================================================
def generate_report(config_path="config.yaml", output_path="results/lifetime_pd_report.xlsx"):
print("=" * 60)
print(" Lifetime PD 분석 보고서 생성")
print("=" * 60)
with open(config_path) as f:
config = yaml.safe_load(f)
rho = config.get("model",{}).get("rho", 0.20)
grades = config.get("model",{}).get("rating_grades", list(RATING_GRADES))
forced_vars = config.get("model",{}).get("macro_vars", [])
macro_method = config.get("model",{}).get("macro_method", "ar1_macro")
horizon = config.get("convergence",{}).get("total_horizon", 50)
from data.pd_floor import apply_pd_floor_to_matrices, build_complete_pd_floor_table
# 1. 데이터
print("\n [1/6] 데이터 로딩...")
data_config = config.get("data", {})
tm_source = data_config.get("transition_source", "real")
tm_dir = data_config.get("transition_dir", None)
tm_all = load_transition_matrices(tm_source, data_dir=tm_dir)
# 2000-2025 필터
tm_raw = {y:m for y,m in tm_all.items() if 2000 <= y <= 2025}
# KAP 채권 YTM 기반 PD Floor 적용
pd_floors, _, pd_floors_full = build_complete_pd_floor_table()
tm = apply_pd_floor_to_matrices(tm_raw, pd_floors)
ttc = compute_ttc_matrix(tm)
# 거시변수
macro_data = _fallback_macro_data()
try:
ecos = load_ecos_macro()
if ecos is not None and not ecos.empty:
macro_data = pd.concat([macro_data, ecos], axis=1)
macro_data = macro_data.loc[:,~macro_data.columns.duplicated()]
except: pass
derived = compute_derived_features(macro_data)
if not derived.empty:
macro_data = pd.concat([macro_data, derived], axis=1)
macro_data = macro_data.loc[:,~macro_data.columns.duplicated()]
# 확장 변환: LAG2, log, diff, pctchg, log-return
base_cols = list(macro_data.columns)
for col in base_cols:
s = macro_data[col]
d = s.diff()
if d.std() > 1e-10:
macro_data[f"{col}_D"] = d
pc = s.pct_change().replace([np.inf, -np.inf], np.nan)
if pc.dropna().std() > 1e-10:
macro_data[f"{col}_R"] = pc
if (s > 0).all():
ls = np.log(s)
if ls.std() > 1e-10:
macro_data[f"{col}_L"] = ls
ld = ls.diff()
if ld.dropna().std() > 1e-10:
macro_data[f"{col}_LR"] = ld
l2 = s.shift(1)
if l2.dropna().std() > 1e-10:
macro_data[f"{col}_LAG2"] = l2
macro_data = macro_data.ffill().bfill()
macro_data = macro_data.loc[:,~macro_data.columns.duplicated()]
print(f" 전이행렬: {len(tm)}개년 [{tm_source}], PD Floor 적용, 거시변수: {len(macro_data.columns)}")
# 2. Zt
print(" [2/6] Zt 추정...")
zt_dict = estimate_zt_series(tm, ttc, rho)
# 3. AR(1)
print(" [3/6] AR(1)+Macro 적합...")
model = build_macro_zt_model(zt_dict, macro_data, method=macro_method, forced_vars=forced_vars)
diag = model.diagnostics()
# 추가 진단 통계 (AR1 시트 6개 검정용)
zt_arr = np.array([zt_dict[yr] for yr in sorted(zt_dict.keys())])
from statsmodels.tsa.stattools import adfuller as _adfuller
_adf = _adfuller(zt_arr, autolag="BIC")
diag["adf_stat"] = _adf[0]; diag["adf_pvalue"] = _adf[1]
if model.result is not None:
_resid = model.result.resid
_exog = model.result.model.exog
from statsmodels.stats.diagnostic import acorr_ljungbox as _lb, het_breuschpagan as _bp, het_arch as _arch
from scipy.stats import shapiro as _shapiro
try:
_lbr = _lb(_resid, lags=[5], return_df=True)
diag["ljung_box_stat"] = float(_lbr["lb_stat"].iloc[0])
diag["ljung_box_pvalue"] = float(_lbr["lb_pvalue"].iloc[0])
except: pass
try:
_bpr = _bp(_resid, _exog)
diag["bp_stat"] = float(_bpr[0]); diag["bp_pvalue"] = float(_bpr[1])
except: pass
try:
_ar = _arch(_resid, nlags=3)
diag["arch_stat"] = float(_ar[0]); diag["arch_pvalue"] = float(_ar[1])
except: pass
try:
_sw = _shapiro(_resid)
diag["shapiro_stat"] = float(_sw.statistic); diag["shapiro_pvalue"] = float(_sw.pvalue)
except: pass
diag["bic"] = float(model.result.bic) if hasattr(model.result, 'bic') else None
print(f" φ={model.ar1_phi:.4f}, R²={diag['r_squared']:.4f}, Adj.R²={diag['adj_r_squared']:.4f}")
# 4. 시나리오
print(" [4/6] 시나리오 Z경로...")
engine = ScenarioEngine(config)
z_paths = engine.generate_z_paths(zt_dict, macro_model=model)
weights = engine.get_scenario_weights()
# 5. Lifetime PD
print(" [5/6] Lifetime PD 산출...")
ttc_8x8 = expand_to_8x8(ttc) if ttc.shape == (7,7) else ttc
pd_engine = LifetimePDEngine(ttc_8x8, rho, rating_grades=RATING_GRADES_8)
pd_results = pd_engine.compute_all_scenarios(z_paths, weights, horizon)
# 6. 검증
print(" [6/6] 통계 검증...")
zt_series = pd.Series(zt_dict)
reg_result = model.result
val_df = run_full_validation(zt_series.values, reg_result, pd_results, list(RATING_GRADES[:-1]))
# ================================================================
# Excel 생성
# ================================================================
print(f"\n Excel 보고서 생성 중...")
wb = Workbook()
sheet_summary(wb, config, model, zt_dict, diag, z_paths, val_df, pd_engine, pd_results, RATING_GRADES_8)
sheet_tm(wb, tm_raw, tm, ttc, pd_floors, config)
sheet_macro(wb, macro_data, forced_vars)
sheet_zt(wb, zt_dict, macro_data, forced_vars, rho)
sheet_ar1(wb, model, diag)
sheet_zpath(wb, z_paths, config)
sheet_pd(wb, pd_results, config, RATING_GRADES_8)
sheet_weighted(wb, pd_results, config, RATING_GRADES_8)
sheet_validation(wb, val_df)
os.makedirs(os.path.dirname(output_path) or '.', exist_ok=True)
wb.save(output_path)
print(f"\n ✓ 보고서 저장: {output_path}")
print(f" 시트: {len(wb.sheetnames)}개 ({', '.join(wb.sheetnames)})")
return output_path
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Lifetime PD 보고서 생성")
parser.add_argument("--config", default="config.yaml")
parser.add_argument("--output", default="results/lifetime_pd_report.xlsx")
args = parser.parse_args()
generate_report(args.config, args.output)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

376
results/report_preview.html Normal file
View File

@@ -0,0 +1,376 @@
<!DOCTYPE html>
<html lang="ko"><head><meta charset="utf-8">
<title>Lifetime PD Report Preview</title>
<style>
body { font-family: 'Malgun Gothic', sans-serif; background: #f5f5f5; margin: 20px; }
.sheet { background: white; border-radius: 8px; padding: 20px; margin-bottom: 30px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.sheet-title { font-size: 18px; font-weight: bold; color: #1F3864; border-bottom: 3px solid #2B5797; padding-bottom: 8px; margin-bottom: 15px; }
table { border-collapse: collapse; width: 100%; font-size: 11px; }
th { background: #2B5797; color: white; padding: 6px 8px; text-align: center; border: 1px solid #B4C6E7; }
td { padding: 5px 8px; border: 1px solid #D6E4F0; }
tr:nth-child(even) td { background: #EDF2F9; }
.merged { background: #1F3864; color: white; font-size: 14px; font-weight: bold; padding: 10px; }
.section { background: #D6E4F0; font-weight: bold; color: #1F3864; padding: 6px; }
.num { text-align: right; font-family: Consolas, monospace; }
.pass { background: #E2EFDA; color: #2E7D32; font-weight: bold; }
.fail { background: #FCE4EC; color: #C62828; font-weight: bold; }
.nav { position: sticky; top: 0; background: #1F3864; padding: 10px 20px; border-radius: 8px; margin-bottom: 20px; z-index: 100; }
.nav a { color: white; text-decoration: none; margin-right: 15px; font-size: 12px; }
.nav a:hover { text-decoration: underline; }
</style></head><body>
<div class="nav"><a href="#요약">요약</a><a href="#원시데이터_전이행렬">원시데이터_전이행렬</a><a href="#원시데이터_거시변수">원시데이터_거시변수</a><a href="#Zt_추정">Zt_추정</a><a href="#AR1_모형">AR1_모형</a><a href="#시나리오_Z경로">시나리오_Z경로</a><a href="#Lifetime_PD">Lifetime_PD</a><a href="#가중평균_PD">가중평균_PD</a><a href="#검증결과">검증결과</a></div>
<div class="sheet" id="요약">
<div class="sheet-title">📊 요약</div>
<table>
<tr><td rowspan="1" colspan="10" class="merged"> Lifetime PD 분석 보고서</td></tr>
<tr><td></td><td>생성일시: 2026-03-11 20:50</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 1. 모형 개요</td></tr>
<tr><td></td><td>모형 구조</td><td>Z(t) = c + φ·Z(t-1) + β₁·X₁_std + β₂·X₂_std + β₃·X₃_std + ε</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>모형 유형</td><td>AR(1) + Macro (Vasicek Single-Factor)</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>적용 기준</td><td>IFRS 9 (2018, 2024 개정)</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>변수 선택</td><td>HOUSING_PRICE, CURRENT_ACCOUNT, CREDIT_SPREAD_LAG1</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 2. AR(1) 모형 파라미터</td></tr>
<tr><td></td><td>자기회귀 계수 (φ)</td><td class="num">-0.3352</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>반감기</td><td>0.6년</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>절편 (c)</td><td class="num">-0.1781</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>β(HOUSING_PRICE)</td><td class="num">0.6412</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>β(CURRENT_ACCOUNT)</td><td class="num">-0.1771</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>β(CREDIT_SPREAD_LAG1)</td><td class="num">-0.6439</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>잔차 σ</td><td class="num">0.3989</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>장기 균형 Z</td><td class="num">-0.1334</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 3. 모형 적합도</td></tr>
<tr><td></td><td></td><td class="num">0.6667</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>Adj. R²</td><td class="num">0.6000</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>F p-value</td><td class="num">0.000130</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>AIC</td><td class="num">34.9959</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>DW</td><td class="num">2.8731</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 4. 시나리오 설정</td></tr>
<tr><td></td><td>시나리오</td><td>가중치</td><td>HOUSING_PRICE (σ)</td><td>CURRENT_ACCOUNT (σ)</td><td>CREDIT_SPREAD_LAG1 (σ)</td><td>Z(t+1)</td><td></td><td></td><td></td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">0.2000</td><td class="num">1</td><td class="num">1</td><td class="num">-1</td><td class="num">1.0527</td><td></td><td></td><td></td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">0.5000</td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">-0.0553</td><td></td><td></td><td></td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">0.3000</td><td class="num">-1.5000</td><td class="num">-1.5000</td><td class="num">1.5000</td><td class="num">-1.7174</td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 5. 1년차 확률가중 PD (%)</td></tr>
<tr><td></td><td></td><td>AAA</td><td>AA</td><td>A</td><td>BBB</td><td>BB</td><td>B</td><td>CCC</td><td></td></tr>
<tr><td></td><td>가중PD(1Y)</td><td class="num">0.0788</td><td class="num">0.1085</td><td class="num">0.2817</td><td class="num">1.7066</td><td class="num">6.4614</td><td class="num">13.0089</td><td class="num">31.6688</td><td></td></tr>
</table>
</div>
<div class="sheet" id="원시데이터_전이행렬">
<div class="sheet-title">📊 원시데이터_전이행렬</div>
<table>
<tr><td rowspan="1" colspan="9" class="merged"> 전이행렬 파이프라인: Original → PD Floor → TTC</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="9" class="section"> PD Floor 기준 (Basel III CRE30.4 + S&P/Moody's)</td></tr>
<tr><td></td><td>등급</td><td>Floor PD (bp)</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>AAA</td><td class="num">5</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>AA</td><td class="num">5</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>A</td><td class="num">7</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>BBB</td><td class="num">20</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>BB</td><td class="num">60</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>B</td><td class="num">300</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="9" class="section"> TTC 전이행렬 (PD Floor 적용 후, 1998~2025 평균)</td></tr>
<tr><td></td><td>From\To</td><td>AAA</td><td>AA</td><td>A</td><td>BBB</td><td>BB</td><td>B</td><td>D</td></tr>
<tr><td></td><td>AAA</td><td class="num">0.9911</td><td class="num">0.008428</td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">0.000510</td></tr>
<tr><td></td><td>AA</td><td class="num">0.0173</td><td class="num">0.9417</td><td class="num">0.0367</td><td class="num">0.003558</td><td class="num">0.000007</td><td class="num">0.000007</td><td class="num">0.000733</td></tr>
<tr><td></td><td>A</td><td class="num">0</td><td class="num">0.0532</td><td class="num">0.8965</td><td class="num">0.0429</td><td class="num">0.001260</td><td class="num">0.004052</td><td class="num">0.002048</td></tr>
<tr><td></td><td>BBB</td><td class="num">0</td><td class="num">0.000012</td><td class="num">0.0659</td><td class="num">0.8642</td><td class="num">0.0343</td><td class="num">0.0215</td><td class="num">0.0140</td></tr>
<tr><td></td><td>BB</td><td class="num">0</td><td class="num">0</td><td class="num">0.003234</td><td class="num">0.0435</td><td class="num">0.8036</td><td class="num">0.0877</td><td class="num">0.0619</td></tr>
<tr><td></td><td>B</td><td class="num">0</td><td class="num">0</td><td class="num">0.000735</td><td class="num">0.003678</td><td class="num">0.0277</td><td class="num">0.8248</td><td class="num">0.1431</td></tr>
<tr><td></td><td>D</td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">1</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="9" class="section"> 연도별 D열 비교 (Original vs Floor Calibrated)</td></tr>
<tr><td></td><td>연도</td><td>AAA(원시)</td><td>AA(원시)</td><td>A(원시)</td><td>BBB(원시)</td><td>BB(원시)</td><td>B(원시)</td><td></td></tr>
<tr><td></td><td>연도</td><td>AAA</td><td>AA</td><td>A</td><td>BBB</td><td>BB</td><td>B</td><td></td></tr>
<tr><td></td><td>1998(원시)</td><td class="num">0</td><td class="num">0</td><td class="num">16.9400</td><td class="num">105.83</td><td class="num">1436.40</td><td class="num">3944.30</td><td></td></tr>
<tr><td></td><td>1998(보정)</td><td class="num">5</td><td class="num">5</td><td class="num">16.9400</td><td class="num">105.83</td><td class="num">1436.40</td><td class="num">3944.30</td><td></td></tr>
<tr><td></td><td>1999(원시)</td><td class="num">1.8000</td><td class="num">2.5000</td><td class="num">16.5700</td><td class="num">106.39</td><td class="num">198.15</td><td class="num">1881.36</td><td></td></tr>
<tr><td></td><td>1999(보정)</td><td class="num">5</td><td class="num">5</td><td class="num">16.5700</td><td class="num">106.39</td><td class="num">198.15</td><td class="num">1881.36</td><td></td></tr>
<tr><td></td><td>2000(원시)</td><td class="num">1.8200</td><td class="num">5.0600</td><td class="num">16.5200</td><td class="num">101.54</td><td class="num">333.23</td><td class="num">983.95</td><td></td></tr>
<tr><td></td><td>2000(보정)</td><td class="num">5.0000</td><td class="num">5.0600</td><td class="num">16.5200</td><td class="num">101.54</td><td class="num">333.23</td><td class="num">983.95</td><td></td></tr>
<tr><td></td><td>2001(원시)</td><td class="num">4.0600</td><td class="num">7.9900</td><td class="num">17.3600</td><td class="num">149.54</td><td class="num">499.89</td><td class="num">1792.24</td><td></td></tr>
<tr><td></td><td>2001(보정)</td><td class="num">5.0000</td><td class="num">7.9900</td><td class="num">17.3600</td><td class="num">149.54</td><td class="num">499.89</td><td class="num">1792.24</td><td></td></tr>
<tr><td></td><td>2002(원시)</td><td class="num">1.7800</td><td class="num">8.3100</td><td class="num">17.1900</td><td class="num">129.25</td><td class="num">479.37</td><td class="num">2136.03</td><td></td></tr>
<tr><td></td><td>2002(보정)</td><td class="num">5.0000</td><td class="num">8.3100</td><td class="num">17.1900</td><td class="num">129.25</td><td class="num">479.37</td><td class="num">2136.03</td><td></td></tr>
<tr><td></td><td>2003(원시)</td><td class="num">3.7700</td><td class="num">7.7500</td><td class="num">16.2300</td><td class="num">108.94</td><td class="num">500.14</td><td class="num">867.74</td><td></td></tr>
<tr><td></td><td>2003(보정)</td><td class="num">5</td><td class="num">7.7500</td><td class="num">16.2300</td><td class="num">108.94</td><td class="num">500.14</td><td class="num">867.74</td><td></td></tr>
<tr><td></td><td>2004(원시)</td><td class="num">5.7900</td><td class="num">8.4200</td><td class="num">17.7000</td><td class="num">111.19</td><td class="num">2047.86</td><td class="num">2152.92</td><td></td></tr>
<tr><td></td><td>2004(보정)</td><td class="num">5.7900</td><td class="num">8.4200</td><td class="num">17.7000</td><td class="num">111.19</td><td class="num">2047.86</td><td class="num">2152.92</td><td></td></tr>
<tr><td></td><td>2005(원시)</td><td class="num">5.4300</td><td class="num">8.4900</td><td class="num">17.9100</td><td class="num">100.93</td><td class="num">239.44</td><td class="num">1652.70</td><td></td></tr>
<tr><td></td><td>2005(보정)</td><td class="num">5.4300</td><td class="num">8.4900</td><td class="num">17.9100</td><td class="num">100.93</td><td class="num">239.44</td><td class="num">1652.70</td><td></td></tr>
<tr><td></td><td>2006(원시)</td><td class="num">3.5600</td><td class="num">7.6400</td><td class="num">15.8000</td><td class="num">124.46</td><td class="num">259.23</td><td class="num">339.46</td><td></td></tr>
<tr><td></td><td>2006(보정)</td><td class="num">5.0000</td><td class="num">7.6400</td><td class="num">15.8000</td><td class="num">124.46</td><td class="num">259.23</td><td class="num">339.46</td><td></td></tr>
<tr><td></td><td>2007(원시)</td><td class="num">5.1500</td><td class="num">7.7700</td><td class="num">18.9900</td><td class="num">100.13</td><td class="num">317.06</td><td class="num">623.64</td><td></td></tr>
<tr><td></td><td>2007(보정)</td><td class="num">5.1500</td><td class="num">7.7700</td><td class="num">18.9900</td><td class="num">100.13</td><td class="num">317.06</td><td class="num">623.64</td><td></td></tr>
<tr><td></td><td>2008(원시)</td><td class="num">3.4600</td><td class="num">7.6000</td><td class="num">16.0100</td><td class="num">165.80</td><td class="num">847.65</td><td class="num">739.98</td><td></td></tr>
<tr><td></td><td>2008(보정)</td><td class="num">5</td><td class="num">7.6000</td><td class="num">16.0100</td><td class="num">165.80</td><td class="num">847.65</td><td class="num">739.98</td><td></td></tr>
<tr><td></td><td>2009(원시)</td><td class="num">5.2000</td><td class="num">7.4800</td><td class="num">15.7600</td><td class="num">122.28</td><td class="num">1444.45</td><td class="num">1310.57</td><td></td></tr>
<tr><td></td><td>2009(보정)</td><td class="num">5.2000</td><td class="num">7.4800</td><td class="num">15.7600</td><td class="num">122.28</td><td class="num">1444.45</td><td class="num">1310.57</td><td></td></tr>
<tr><td></td><td>2010(원시)</td><td class="num">5.1000</td><td class="num">7.4700</td><td class="num">16.5900</td><td class="num">127.38</td><td class="num">1171.52</td><td class="num">2557.17</td><td></td></tr>
<tr><td></td><td>2010(보정)</td><td class="num">5.1000</td><td class="num">7.4700</td><td class="num">16.5900</td><td class="num">127.38</td><td class="num">1171.52</td><td class="num">2557.17</td><td></td></tr>
<tr><td></td><td>2011(원시)</td><td class="num">3.4000</td><td class="num">7.4500</td><td class="num">15.8700</td><td class="num">234.21</td><td class="num">530.64</td><td class="num">1499.83</td><td></td></tr>
<tr><td></td><td>2011(보정)</td><td class="num">5.0000</td><td class="num">7.4500</td><td class="num">15.8700</td><td class="num">234.21</td><td class="num">530.64</td><td class="num">1499.83</td><td></td></tr>
<tr><td></td><td>2012(원시)</td><td class="num">3.4200</td><td class="num">7.5400</td><td class="num">64.5399</td><td class="num">222.52</td><td class="num">811.32</td><td class="num">3796.36</td><td></td></tr>
<tr><td></td><td>2012(보정)</td><td class="num">5</td><td class="num">7.5400</td><td class="num">64.5399</td><td class="num">222.52</td><td class="num">811.32</td><td class="num">3796.36</td><td></td></tr>
<tr><td></td><td>2013(원시)</td><td class="num">1.7500</td><td class="num">7.4500</td><td class="num">15.7700</td><td class="num">402.43</td><td class="num">790.61</td><td class="num">1011.51</td><td></td></tr>
<tr><td></td><td>2013(보정)</td><td class="num">5.0000</td><td class="num">7.4500</td><td class="num">15.7700</td><td class="num">402.43</td><td class="num">790.61</td><td class="num">1011.51</td><td></td></tr>
<tr><td></td><td>2014(원시)</td><td class="num">5.1700</td><td class="num">7.4200</td><td class="num">68.6300</td><td class="num">210.13</td><td class="num">669.82</td><td class="num">688.92</td><td></td></tr>
<tr><td></td><td>2014(보정)</td><td class="num">5.1700</td><td class="num">7.4200</td><td class="num">68.6300</td><td class="num">210.13</td><td class="num">669.82</td><td class="num">688.92</td><td></td></tr>
<tr><td></td><td>2015(원시)</td><td class="num">5.4400</td><td class="num">7.3400</td><td class="num">17.1000</td><td class="num">113.01</td><td class="num">1003.99</td><td class="num">1469.78</td><td></td></tr>
<tr><td></td><td>2015(보정)</td><td class="num">5.4400</td><td class="num">7.3400</td><td class="num">17.1000</td><td class="num">113.01</td><td class="num">1003.99</td><td class="num">1469.78</td><td></td></tr>
<tr><td></td><td>2016(원시)</td><td class="num">5.1600</td><td class="num">7.3400</td><td class="num">15.9700</td><td class="num">115.10</td><td class="num">526.32</td><td class="num">1094.35</td><td></td></tr>
<tr><td></td><td>2016(보정)</td><td class="num">5.1600</td><td class="num">7.3400</td><td class="num">15.9700</td><td class="num">115.10</td><td class="num">526.32</td><td class="num">1094.35</td><td></td></tr>
<tr><td></td><td>2017(원시)</td><td class="num">3.3900</td><td class="num">7.3600</td><td class="num">17.8200</td><td class="num">123.67</td><td class="num">232.74</td><td class="num">584.60</td><td></td></tr>
<tr><td></td><td>2017(보정)</td><td class="num">5</td><td class="num">7.3600</td><td class="num">17.8200</td><td class="num">123.67</td><td class="num">232.74</td><td class="num">584.60</td><td></td></tr>
<tr><td></td><td>2018(원시)</td><td class="num">0</td><td class="num">7.5200</td><td class="num">17.6600</td><td class="num">141.91</td><td class="num">217.93</td><td class="num">485.42</td><td></td></tr>
<tr><td></td><td>2018(보정)</td><td class="num">5</td><td class="num">7.5200</td><td class="num">17.6600</td><td class="num">141.91</td><td class="num">217.93</td><td class="num">485.42</td><td></td></tr>
<tr><td></td><td>2019(원시)</td><td class="num">1.6900</td><td class="num">7.5100</td><td class="num">16.5700</td><td class="num">108.98</td><td class="num">721.38</td><td class="num">1451.42</td><td></td></tr>
<tr><td></td><td>2019(보정)</td><td class="num">5</td><td class="num">7.5100</td><td class="num">16.5700</td><td class="num">108.98</td><td class="num">721.38</td><td class="num">1451.42</td><td></td></tr>
<tr><td></td><td>2020(원시)</td><td class="num">5.0800</td><td class="num">7.4300</td><td class="num">15.9200</td><td class="num">104.60</td><td class="num">180.18</td><td class="num">499.99</td><td></td></tr>
<tr><td></td><td>2020(보정)</td><td class="num">5.0800</td><td class="num">7.4300</td><td class="num">15.9200</td><td class="num">104.60</td><td class="num">180.18</td><td class="num">499.99</td><td></td></tr>
<tr><td></td><td>2021(원시)</td><td class="num">5.1700</td><td class="num">7.2500</td><td class="num">16.5700</td><td class="num">105.93</td><td class="num">216.50</td><td class="num">544.44</td><td></td></tr>
<tr><td></td><td>2021(보정)</td><td class="num">5.1700</td><td class="num">7.2500</td><td class="num">16.5700</td><td class="num">105.93</td><td class="num">216.50</td><td class="num">544.44</td><td></td></tr>
<tr><td></td><td>2022(원시)</td><td class="num">3.4700</td><td class="num">7.3400</td><td class="num">16.6100</td><td class="num">106.95</td><td class="num">117.13</td><td class="num">622.53</td><td></td></tr>
<tr><td></td><td>2022(보정)</td><td class="num">5</td><td class="num">7.3400</td><td class="num">16.6100</td><td class="num">106.95</td><td class="num">117.13</td><td class="num">622.53</td><td></td></tr>
<tr><td></td><td>2023(원시)</td><td class="num">1.8000</td><td class="num">7.6400</td><td class="num">20.9400</td><td class="num">160.26</td><td class="num">982.60</td><td class="num">2376.81</td><td></td></tr>
<tr><td></td><td>2023(보정)</td><td class="num">5</td><td class="num">7.6400</td><td class="num">20.9400</td><td class="num">160.26</td><td class="num">982.60</td><td class="num">2376.81</td><td></td></tr>
<tr><td></td><td>2024(원시)</td><td class="num">5.1300</td><td class="num">7.2700</td><td class="num">17.4800</td><td class="num">118.96</td><td class="num">243.33</td><td class="num">1196.36</td><td></td></tr>
<tr><td></td><td>2024(보정)</td><td class="num">5.1300</td><td class="num">7.2700</td><td class="num">17.4800</td><td class="num">118.96</td><td class="num">243.33</td><td class="num">1196.36</td><td></td></tr>
<tr><td></td><td>2025(원시)</td><td class="num">5.1200</td><td class="num">7.4100</td><td class="num">16.5500</td><td class="num">107.88</td><td class="num">321.42</td><td class="num">1764.13</td><td></td></tr>
<tr><td></td><td>2025(보정)</td><td class="num">5.1200</td><td class="num">7.4100</td><td class="num">16.5500</td><td class="num">107.88</td><td class="num">321.42</td><td class="num">1764.13</td><td></td></tr>
</table>
</div>
<div class="sheet" id="원시데이터_거시변수">
<div class="sheet-title">📊 원시데이터_거시변수</div>
<table>
<tr><td rowspan="1" colspan="42" class="merged"> 원시 데이터: 거시경제변수</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="42" class="section"> ★ 선택 변수: HOUSING_PRICE, CURRENT_ACCOUNT, CREDIT_SPREAD_LAG1</td></tr>
<tr><td></td><td>연도</td><td>HOUSING_PRICE</td><td>CURRENT_ACCOUNT</td><td>CREDIT_SPREAD_LAG1</td><td>GDP_GROWTH</td><td>UNEMPLOYMENT</td><td>BASE_RATE</td><td>CD_RATE</td><td>CPI_GROWTH</td><td>LEADING_INDEX</td><td>GOVT_3Y</td><td>CORP_AA</td><td>CORP_BBB</td><td>IPI</td><td>EXPORT</td><td>GOVT_10Y</td><td>IMPORT_AMT</td><td>USDKRW</td><td>M2</td><td>CSI</td><td>KOSPI</td><td>IMPORT_PRICE</td><td>DISHONOR_RATE</td><td>HOUSEHOLD_DEBT</td><td>FACILITY_INVEST</td><td>RETAIL_SALES</td><td>EMPLOYED</td><td>EMPLOYMENT_RATE</td><td>OIL_PRICE</td><td>COINCIDENT</td><td>BSI_MANUF</td><td>CONSTRUCTION_DONE</td><td>SPI</td><td>CONSTR_INVEST_GR</td><td>GFCF_GROWTH</td><td>SAVING_RATE</td><td>INVEST_RATE</td><td>TRADE_GNI</td><td>MANUF_CAPACITY</td><td>IPI_LAG1</td><td>EXPORT_DIFF</td></tr>
<tr><td></td><td class="num">2000</td><td class="num">55.2000</td><td class="num">123.50</td><td></td><td class="num">8.9000</td><td class="num">4.4000</td><td class="num">5.2500</td><td class="num">7.0900</td><td class="num">2.3000</td><td class="num">101.20</td><td class="num">8.3500</td><td class="num">9.3500</td><td class="num">11.9000</td><td class="num">102.50</td><td class="num">172268</td><td class="num">8.5500</td><td class="num">160481</td><td class="num">1131</td><td class="num">651.80</td><td class="num">101</td><td class="num">504</td><td class="num">78.5000</td><td class="num">0.4600</td><td class="num">194</td><td class="num">62.5000</td><td class="num">72</td><td class="num">2115</td><td class="num">58.5000</td><td class="num">26.2000</td><td class="num">99.8000</td><td class="num">90</td><td class="num">56.3000</td><td class="num">58</td><td class="num">-1.4000</td><td class="num">11.4000</td><td class="num">33.7000</td><td class="num">31</td><td class="num">72.5000</td><td class="num">109.50</td><td></td><td></td></tr>
<tr><td></td><td class="num">2001</td><td class="num">56.8000</td><td class="num">80.3000</td><td class="num">2.5500</td><td class="num">4.5000</td><td class="num">4</td><td class="num">4</td><td class="num">5.3400</td><td class="num">4.1000</td><td class="num">99.5000</td><td class="num">6.7000</td><td class="num">8.1200</td><td class="num">11.2700</td><td class="num">99.5000</td><td class="num">150439</td><td class="num">7.0500</td><td class="num">141098</td><td class="num">1291</td><td class="num">736.50</td><td class="num">96.5000</td><td class="num">694</td><td class="num">73.6000</td><td class="num">0.2800</td><td class="num">225</td><td class="num">58.5000</td><td class="num">73.5000</td><td class="num">2118</td><td class="num">59</td><td class="num">22.8000</td><td class="num">98</td><td class="num">82</td><td class="num">53.8000</td><td class="num">60.2000</td><td class="num">5.6000</td><td class="num">0.6000</td><td class="num">31.7000</td><td class="num">29.3000</td><td class="num">66.3000</td><td class="num">105.80</td><td class="num">102.50</td><td class="num">-21829</td></tr>
<tr><td></td><td class="num">2002</td><td class="num">65.3000</td><td class="num">53.9000</td><td class="num">3.1500</td><td class="num">7.4000</td><td class="num">3.3000</td><td class="num">4.2500</td><td class="num">4.9900</td><td class="num">2.8000</td><td class="num">102.30</td><td class="num">6.0600</td><td class="num">7.0200</td><td class="num">9.7500</td><td class="num">108.50</td><td class="num">162471</td><td class="num">6.5800</td><td class="num">152126</td><td class="num">1251</td><td class="num">816.30</td><td class="num">105</td><td class="num">628</td><td class="num">72.1000</td><td class="num">0.1800</td><td class="num">306</td><td class="num">63.2000</td><td class="num">76</td><td class="num">2217</td><td class="num">60</td><td class="num">23.7000</td><td class="num">101.50</td><td class="num">92</td><td class="num">55.2000</td><td class="num">63.5000</td><td class="num">6.5000</td><td class="num">6.7000</td><td class="num">31.3000</td><td class="num">29.1000</td><td class="num">62.4000</td><td class="num">110.50</td><td class="num">99.5000</td><td class="num">12032</td></tr>
<tr><td></td><td class="num">2003</td><td class="num">71.5000</td><td class="num">119.50</td><td class="num">2.7300</td><td class="num">2.9000</td><td class="num">3.6000</td><td class="num">3.7500</td><td class="num">4.2400</td><td class="num">3.5000</td><td class="num">98.8000</td><td class="num">4.9300</td><td class="num">5.7000</td><td class="num">8.9700</td><td class="num">109.80</td><td class="num">193817</td><td class="num">5.4500</td><td class="num">178827</td><td class="num">1192</td><td class="num">879.20</td><td class="num">96</td><td class="num">811</td><td class="num">81.3000</td><td class="num">0.1200</td><td class="num">360</td><td class="num">60.5000</td><td class="num">74</td><td class="num">2212</td><td class="num">59.5000</td><td class="num">26.8000</td><td class="num">99.2000</td><td class="num">85</td><td class="num">58</td><td class="num">64.8000</td><td class="num">10</td><td class="num">4</td><td class="num">32.6000</td><td class="num">30</td><td class="num">65</td><td class="num">108.20</td><td class="num">108.50</td><td class="num">31346</td></tr>
<tr><td></td><td class="num">2004</td><td class="num">71</td><td class="num">284.20</td><td class="num">3.2700</td><td class="num">4.9000</td><td class="num">3.7000</td><td class="num">3.2500</td><td class="num">3.7700</td><td class="num">3.6000</td><td class="num">100.50</td><td class="num">4.1100</td><td class="num">4.7200</td><td class="num">7.5300</td><td class="num">119.20</td><td class="num">253845</td><td class="num">4.7300</td><td class="num">224463</td><td class="num">1145</td><td class="num">935.30</td><td class="num">97</td><td class="num">896</td><td class="num">90.5000</td><td class="num">0.0800</td><td class="num">394</td><td class="num">66.5000</td><td class="num">74.5000</td><td class="num">2272</td><td class="num">59.8000</td><td class="num">33.5000</td><td class="num">100.80</td><td class="num">88</td><td class="num">63.5000</td><td class="num">66</td><td class="num">1.8000</td><td class="num">2.1000</td><td class="num">34.8000</td><td class="num">30.3000</td><td class="num">73.5000</td><td class="num">113.80</td><td class="num">109.80</td><td class="num">60028</td></tr>
<tr><td></td><td class="num">2005</td><td class="num">73.5000</td><td class="num">149.80</td><td class="num">2.8100</td><td class="num">3.9000</td><td class="num">3.7000</td><td class="num">3.7500</td><td class="num">3.8100</td><td class="num">2.8000</td><td class="num">101.80</td><td class="num">4.2700</td><td class="num">4.6800</td><td class="num">6.5100</td><td class="num">126</td><td class="num">284419</td><td class="num">4.9500</td><td class="num">261238</td><td class="num">1024</td><td class="num">1002.70</td><td class="num">100.50</td><td class="num">1011</td><td class="num">99.2000</td><td class="num">0.0600</td><td class="num">440</td><td class="num">68</td><td class="num">76.5000</td><td class="num">2297</td><td class="num">60.3000</td><td class="num">49.3000</td><td class="num">101.20</td><td class="num">92</td><td class="num">66</td><td class="num">68.5000</td><td class="num">-0.4000</td><td class="num">1.9000</td><td class="num">33.4000</td><td class="num">29.7000</td><td class="num">72.5000</td><td class="num">114.50</td><td class="num">119.20</td><td class="num">30574</td></tr>
<tr><td></td><td class="num">2006</td><td class="num">80.2000</td><td class="num">53.9000</td><td class="num">1.8300</td><td class="num">5.2000</td><td class="num">3.5000</td><td class="num">4.5000</td><td class="num">4.7200</td><td class="num">2.2000</td><td class="num">102.50</td><td class="num">4.8300</td><td class="num">5.2500</td><td class="num">7.0800</td><td class="num">136</td><td class="num">325465</td><td class="num">5.1700</td><td class="num">309383</td><td class="num">955</td><td class="num">1089.90</td><td class="num">106</td><td class="num">1434</td><td class="num">107.80</td><td class="num">0.0500</td><td class="num">497</td><td class="num">73.5000</td><td class="num">78.5000</td><td class="num">2334</td><td class="num">60.9000</td><td class="num">61.5000</td><td class="num">102.80</td><td class="num">95</td><td class="num">69.5000</td><td class="num">71.2000</td><td class="num">0.5000</td><td class="num">3.4000</td><td class="num">32.5000</td><td class="num">29.6000</td><td class="num">73.2000</td><td class="num">115.80</td><td class="num">126</td><td class="num">41046</td></tr>
<tr><td></td><td class="num">2007</td><td class="num">83.5000</td><td class="num">59.5000</td><td class="num">1.8300</td><td class="num">5.5000</td><td class="num">3.2000</td><td class="num">5</td><td class="num">5.3600</td><td class="num">2.5000</td><td class="num">103.10</td><td class="num">5.2300</td><td class="num">5.7000</td><td class="num">7.4400</td><td class="num">144.50</td><td class="num">371489</td><td class="num">5.4200</td><td class="num">356846</td><td class="num">929</td><td class="num">1181.60</td><td class="num">108.50</td><td class="num">1897</td><td class="num">109.30</td><td class="num">0.0400</td><td class="num">560</td><td class="num">78.5000</td><td class="num">80</td><td class="num">2371</td><td class="num">61.3000</td><td class="num">68.4000</td><td class="num">103.50</td><td class="num">97</td><td class="num">72.8000</td><td class="num">74</td><td class="num">1.4000</td><td class="num">4.2000</td><td class="num">32.4000</td><td class="num">29.4000</td><td class="num">77.8000</td><td class="num">115.20</td><td class="num">136</td><td class="num">46024</td></tr>
<tr><td></td><td class="num">2008</td><td class="num">84</td><td class="num">-57.8000</td><td class="num">1.7400</td><td class="num">2.8000</td><td class="num">3.2000</td><td class="num">3</td><td class="num">5.7000</td><td class="num">4.7000</td><td class="num">96.5000</td><td class="num">5.2700</td><td class="num">7.0200</td><td class="num">10.7300</td><td class="num">148.20</td><td class="num">422007</td><td class="num">5.5700</td><td class="num">435275</td><td class="num">1103</td><td class="num">1263.20</td><td class="num">86</td><td class="num">1124</td><td class="num">132.50</td><td class="num">0.1100</td><td class="num">630</td><td class="num">76</td><td class="num">79</td><td class="num">2385</td><td class="num">61.5000</td><td class="num">94.3000</td><td class="num">98.5000</td><td class="num">72</td><td class="num">74.5000</td><td class="num">75.5000</td><td class="num">-2.8000</td><td class="num">-1.9000</td><td class="num">31.5000</td><td class="num">31.2000</td><td class="num">96.5000</td><td class="num">112.80</td><td class="num">144.50</td><td class="num">50518</td></tr>
<tr><td></td><td class="num">2009</td><td class="num">84.8000</td><td class="num">328.10</td><td class="num">3.7100</td><td class="num">0.8000</td><td class="num">3.6000</td><td class="num">2</td><td class="num">2.6300</td><td class="num">2.8000</td><td class="num">98.2000</td><td class="num">4.0400</td><td class="num">5.8000</td><td class="num">9.2400</td><td class="num">140</td><td class="num">363534</td><td class="num">4.8500</td><td class="num">323085</td><td class="num">1276</td><td class="num">1404.40</td><td class="num">85</td><td class="num">1683</td><td class="num">104.20</td><td class="num">0.1000</td><td class="num">694</td><td class="num">60.5000</td><td class="num">77.5000</td><td class="num">2355</td><td class="num">60.1000</td><td class="num">61.8000</td><td class="num">96.5000</td><td class="num">68</td><td class="num">68.2000</td><td class="num">76</td><td class="num">0.2000</td><td class="num">-1</td><td class="num">31.4000</td><td class="num">26.3000</td><td class="num">82</td><td class="num">102.50</td><td class="num">148.20</td><td class="num">-58473</td></tr>
<tr><td></td><td class="num">2010</td><td class="num">87</td><td class="num">282.10</td><td class="num">3.4400</td><td class="num">6.8000</td><td class="num">3.7000</td><td class="num">2.5000</td><td class="num">2.8000</td><td class="num">2.9000</td><td class="num">103</td><td class="num">3.7200</td><td class="num">4.6600</td><td class="num">7.9800</td><td class="num">161.50</td><td class="num">466384</td><td class="num">4.4900</td><td class="num">425212</td><td class="num">1156</td><td class="num">1504.30</td><td class="num">107</td><td class="num">2051</td><td class="num">115.80</td><td class="num">0.0600</td><td class="num">776</td><td class="num">80.5000</td><td class="num">80.5000</td><td class="num">2397</td><td class="num">60.4000</td><td class="num">78.1000</td><td class="num">103</td><td class="num">95</td><td class="num">72</td><td class="num">78.5000</td><td class="num">-1.4000</td><td class="num">5.8000</td><td class="num">33.5000</td><td class="num">29.5000</td><td class="num">87.9000</td><td class="num">113</td><td class="num">140</td><td class="num">102850</td></tr>
<tr><td></td><td class="num">2011</td><td class="num">89.5000</td><td class="num">184.10</td><td class="num">3.3200</td><td class="num">3.7000</td><td class="num">3.4000</td><td class="num">3.2500</td><td class="num">3.5500</td><td class="num">4</td><td class="num">101.20</td><td class="num">3.6200</td><td class="num">4.4100</td><td class="num">7.7500</td><td class="num">168</td><td class="num">555214</td><td class="num">4.0500</td><td class="num">524413</td><td class="num">1108</td><td class="num">1586.50</td><td class="num">100</td><td class="num">1826</td><td class="num">130.20</td><td class="num">0.0500</td><td class="num">857</td><td class="num">82</td><td class="num">82</td><td class="num">2424</td><td class="num">60.7000</td><td class="num">106</td><td class="num">102.50</td><td class="num">90</td><td class="num">73.5000</td><td class="num">80</td><td class="num">-4.9000</td><td class="num">0.8000</td><td class="num">34</td><td class="num">29.4000</td><td class="num">96.7000</td><td class="num">112.50</td><td class="num">161.50</td><td class="num">88830</td></tr>
<tr><td></td><td class="num">2012</td><td class="num">89</td><td class="num">508.40</td><td class="num">3.3400</td><td class="num">2.4000</td><td class="num">3.2000</td><td class="num">2.7500</td><td class="num">3.1300</td><td class="num">2.2000</td><td class="num">100.30</td><td class="num">3.1300</td><td class="num">3.7600</td><td class="num">6.5600</td><td class="num">168.20</td><td class="num">547870</td><td class="num">3.3500</td><td class="num">519584</td><td class="num">1127</td><td class="num">1673.50</td><td class="num">100.50</td><td class="num">1997</td><td class="num">123.50</td><td class="num">0.0400</td><td class="num">934</td><td class="num">79</td><td class="num">83.5000</td><td class="num">2468</td><td class="num">61.3000</td><td class="num">109.10</td><td class="num">100.50</td><td class="num">85</td><td class="num">72</td><td class="num">82.5000</td><td class="num">-3.2000</td><td class="num">-0.5000</td><td class="num">33.8000</td><td class="num">28.4000</td><td class="num">96.8000</td><td class="num">110.20</td><td class="num">168</td><td class="num">-7344</td></tr>
<tr><td></td><td class="num">2013</td><td class="num">88.8000</td><td class="num">812.10</td><td class="num">2.8000</td><td class="num">3.2000</td><td class="num">3.1000</td><td class="num">2.5000</td><td class="num">2.7200</td><td class="num">1.3000</td><td class="num">100.80</td><td class="num">2.7900</td><td class="num">3.1900</td><td class="num">5.8700</td><td class="num">168.80</td><td class="num">559632</td><td class="num">3.2800</td><td class="num">515586</td><td class="num">1095</td><td class="num">1756.20</td><td class="num">103</td><td class="num">2011</td><td class="num">115</td><td class="num">0.0400</td><td class="num">980</td><td class="num">77.5000</td><td class="num">85</td><td class="num">2503</td><td class="num">61.6000</td><td class="num">105.50</td><td class="num">101</td><td class="num">88</td><td class="num">71.5000</td><td class="num">84</td><td class="num">5.4000</td><td class="num">3.3000</td><td class="num">34</td><td class="num">28.7000</td><td class="num">93.2000</td><td class="num">108</td><td class="num">168.20</td><td class="num">11762</td></tr>
<tr><td></td><td class="num">2014</td><td class="num">90.2000</td><td class="num">843.50</td><td class="num">2.6800</td><td class="num">3.2000</td><td class="num">3.5000</td><td class="num">2</td><td class="num">2.3600</td><td class="num">1.3000</td><td class="num">101</td><td class="num">2.5600</td><td class="num">2.9900</td><td class="num">5.2200</td><td class="num">168.50</td><td class="num">572665</td><td class="num">2.9200</td><td class="num">525515</td><td class="num">1053</td><td class="num">1871</td><td class="num">104</td><td class="num">1916</td><td class="num">105.60</td><td class="num">0.0400</td><td class="num">1050</td><td class="num">81</td><td class="num">86.5000</td><td class="num">2546</td><td class="num">62.4000</td><td class="num">96.7000</td><td class="num">101.50</td><td class="num">90</td><td class="num">73.8000</td><td class="num">86</td><td class="num">1.1000</td><td class="num">3.1000</td><td class="num">34.5000</td><td class="num">29</td><td class="num">87.6000</td><td class="num">108.80</td><td class="num">168.80</td><td class="num">13033</td></tr>
<tr><td></td><td class="num">2015</td><td class="num">95</td><td class="num">1059.40</td><td class="num">2.2300</td><td class="num">2.8000</td><td class="num">3.6000</td><td class="num">1.5000</td><td class="num">1.7200</td><td class="num">0.7000</td><td class="num">100.50</td><td class="num">1.8000</td><td class="num">2.1800</td><td class="num">4.6100</td><td class="num">168</td><td class="num">526757</td><td class="num">2.2500</td><td class="num">436499</td><td class="num">1131</td><td class="num">2010</td><td class="num">103.50</td><td class="num">1961</td><td class="num">79.5000</td><td class="num">0.0300</td><td class="num">1145</td><td class="num">84.5000</td><td class="num">88</td><td class="num">2567</td><td class="num">62.6000</td><td class="num">51.2000</td><td class="num">101</td><td class="num">86</td><td class="num">77.5000</td><td class="num">88.5000</td><td class="num">9.1000</td><td class="num">5.1000</td><td class="num">36</td><td class="num">28.8000</td><td class="num">79.8000</td><td class="num">107.20</td><td class="num">168.50</td><td class="num">-45908</td></tr>
<tr><td></td><td class="num">2016</td><td class="num">97.5000</td><td class="num">992.40</td><td class="num">2.4300</td><td class="num">2.9000</td><td class="num">3.7000</td><td class="num">1.2500</td><td class="num">1.4800</td><td class="num">1</td><td class="num">99.8000</td><td class="num">1.4400</td><td class="num">1.8800</td><td class="num">4.6000</td><td class="num">168.50</td><td class="num">495426</td><td class="num">1.8000</td><td class="num">406193</td><td class="num">1161</td><td class="num">2151.10</td><td class="num">100</td><td class="num">2026</td><td class="num">78</td><td class="num">0.0300</td><td class="num">1250</td><td class="num">82</td><td class="num">89.5000</td><td class="num">2597</td><td class="num">63</td><td class="num">41.3000</td><td class="num">100.20</td><td class="num">85</td><td class="num">89.5000</td><td class="num">90</td><td class="num">10.3000</td><td class="num">5.6000</td><td class="num">36.4000</td><td class="num">29.2000</td><td class="num">74.5000</td><td class="num">106</td><td class="num">168</td><td class="num">-31331</td></tr>
<tr><td></td><td class="num">2017</td><td class="num">100</td><td class="num">752.60</td><td class="num">2.7200</td><td class="num">3.2000</td><td class="num">3.7000</td><td class="num">1.5000</td><td class="num">1.5200</td><td class="num">1.9000</td><td class="num">101.50</td><td class="num">1.8000</td><td class="num">2.2800</td><td class="num">4.8300</td><td class="num">174.20</td><td class="num">573694</td><td class="num">2.3300</td><td class="num">478478</td><td class="num">1131</td><td class="num">2347.20</td><td class="num">105</td><td class="num">2467</td><td class="num">90.5000</td><td class="num">0.0200</td><td class="num">1364</td><td class="num">92</td><td class="num">92</td><td class="num">2620</td><td class="num">63.2000</td><td class="num">53.1000</td><td class="num">101.80</td><td class="num">92</td><td class="num">90</td><td class="num">92.5000</td><td class="num">7.3000</td><td class="num">9.8000</td><td class="num">36.6000</td><td class="num">31.1000</td><td class="num">77.3000</td><td class="num">107.50</td><td class="num">168.50</td><td class="num">78268</td></tr>
<tr><td></td><td class="num">2018</td><td class="num">102</td><td class="num">774.70</td><td class="num">2.5500</td><td class="num">2.9000</td><td class="num">3.8000</td><td class="num">1.7500</td><td class="num">1.8500</td><td class="num">1.5000</td><td class="num">100.80</td><td class="num">2.1000</td><td class="num">2.6700</td><td class="num">5.4100</td><td class="num">178</td><td class="num">604860</td><td class="num">2.5600</td><td class="num">535202</td><td class="num">1100</td><td class="num">2508.90</td><td class="num">102</td><td class="num">2041</td><td class="num">100</td><td class="num">0.0300</td><td class="num">1497</td><td class="num">94.5000</td><td class="num">94</td><td class="num">2633</td><td class="num">63.1000</td><td class="num">69.5000</td><td class="num">101.50</td><td class="num">88</td><td class="num">85.5000</td><td class="num">94.5000</td><td class="num">-4.6000</td><td class="num">-2.4000</td><td class="num">35.9000</td><td class="num">30.3000</td><td class="num">77.3000</td><td class="num">107</td><td class="num">174.20</td><td class="num">31166</td></tr>
<tr><td></td><td class="num">2019</td><td class="num">104.50</td><td class="num">597</td><td class="num">2.7400</td><td class="num">2.2000</td><td class="num">3.8000</td><td class="num">1.2500</td><td class="num">1.6300</td><td class="num">0.4000</td><td class="num">99.3000</td><td class="num">1.5000</td><td class="num">1.9300</td><td class="num">4.5200</td><td class="num">175.50</td><td class="num">542233</td><td class="num">1.7400</td><td class="num">503343</td><td class="num">1166</td><td class="num">2694</td><td class="num">97</td><td class="num">2198</td><td class="num">92.5000</td><td class="num">0.0300</td><td class="num">1573</td><td class="num">89</td><td class="num">96.5000</td><td class="num">2660</td><td class="num">63.5000</td><td class="num">63.4000</td><td class="num">100</td><td class="num">82</td><td class="num">82</td><td class="num">97</td><td class="num">-3.1000</td><td class="num">-2.1000</td><td class="num">34.6000</td><td class="num">30.5000</td><td class="num">72.1000</td><td class="num">102.80</td><td class="num">178</td><td class="num">-62627</td></tr>
<tr><td></td><td class="num">2020</td><td class="num">110</td><td class="num">752.80</td><td class="num">2.5900</td><td class="num">-0.7000</td><td class="num">4</td><td class="num">0.5000</td><td class="num">0.7600</td><td class="num">0.5000</td><td class="num">97</td><td class="num">0.9800</td><td class="num">2.0300</td><td class="num">5.2500</td><td class="num">170</td><td class="num">512498</td><td class="num">1.5200</td><td class="num">467633</td><td class="num">1180</td><td class="num">3070.20</td><td class="num">90</td><td class="num">2873</td><td class="num">85</td><td class="num">0.0200</td><td class="num">1723</td><td class="num">100</td><td class="num">100</td><td class="num">2630</td><td class="num">62.5000</td><td class="num">42.3000</td><td class="num">97.5000</td><td class="num">76</td><td class="num">79</td><td class="num">100</td><td class="num">-0.1000</td><td class="num">2.6000</td><td class="num">36.3000</td><td class="num">31.3000</td><td class="num">65.8000</td><td class="num">100</td><td class="num">175.50</td><td class="num">-29735</td></tr>
<tr><td></td><td class="num">2021</td><td class="num">122</td><td class="num">883</td><td class="num">3.2200</td><td class="num">4.3000</td><td class="num">3.7000</td><td class="num">1</td><td class="num">1.0900</td><td class="num">2.5000</td><td class="num">102.80</td><td class="num">1.4300</td><td class="num">2.2600</td><td class="num">5.6400</td><td class="num">183</td><td class="num">644400</td><td class="num">2.1200</td><td class="num">615093</td><td class="num">1144</td><td class="num">3415.80</td><td class="num">106</td><td class="num">2978</td><td class="num">110.50</td><td class="num">0.0100</td><td class="num">1853</td><td class="num">108.50</td><td class="num">105</td><td class="num">2672</td><td class="num">63.8000</td><td class="num">69.3000</td><td class="num">103</td><td class="num">96</td><td class="num">77.5000</td><td class="num">104.50</td><td class="num">-1.5000</td><td class="num">3.1000</td><td class="num">35.8000</td><td class="num">31.6000</td><td class="num">74.5000</td><td class="num">105.20</td><td class="num">170</td><td class="num">131902</td></tr>
<tr><td></td><td class="num">2022</td><td class="num">128</td><td class="num">258.30</td><td class="num">3.3800</td><td class="num">2.6000</td><td class="num">2.9000</td><td class="num">3.2500</td><td class="num">3.7700</td><td class="num">5.1000</td><td class="num">99.2000</td><td class="num">3.1400</td><td class="num">4.2500</td><td class="num">8.1800</td><td class="num">186.50</td><td class="num">683585</td><td class="num">3.6000</td><td class="num">731370</td><td class="num">1292</td><td class="num">3561</td><td class="num">95</td><td class="num">2237</td><td class="num">140.20</td><td class="num">0.0200</td><td class="num">1903</td><td class="num">105</td><td class="num">107.50</td><td class="num">2726</td><td class="num">64.5000</td><td class="num">97</td><td class="num">100.50</td><td class="num">85</td><td class="num">76</td><td class="num">108</td><td class="num">-3.5000</td><td class="num">-0.7000</td><td class="num">34.5000</td><td class="num">31.8000</td><td class="num">85.2000</td><td class="num">104.50</td><td class="num">183</td><td class="num">39185</td></tr>
<tr><td></td><td class="num">2023</td><td class="num">118</td><td class="num">355.20</td><td class="num">3.9300</td><td class="num">1.4000</td><td class="num">2.7000</td><td class="num">3.5000</td><td class="num">3.7500</td><td class="num">3.6000</td><td class="num">98.8000</td><td class="num">3.5500</td><td class="num">4.4000</td><td class="num">8.4000</td><td class="num">183</td><td class="num">632744</td><td class="num">3.7800</td><td class="num">642756</td><td class="num">1305</td><td class="num">3680</td><td class="num">96.5000</td><td class="num">2655</td><td class="num">120</td><td class="num">0.0300</td><td class="num">1920</td><td class="num">102</td><td class="num">106</td><td class="num">2750</td><td class="num">65</td><td class="num">82.5000</td><td class="num">99.2000</td><td class="num">80</td><td class="num">72</td><td class="num">109.50</td><td class="num">-0.5000</td><td class="num">1.5000</td><td class="num">34</td><td class="num">30.8000</td><td class="num">80.5000</td><td class="num">101</td><td class="num">186.50</td><td class="num">-50841</td></tr>
<tr><td></td><td class="num">2024</td><td class="num">115</td><td class="num">380</td><td class="num">4</td><td class="num">2.2000</td><td class="num">2.8000</td><td class="num">3</td><td class="num">3.3000</td><td class="num">2.3000</td><td class="num">99.5000</td><td class="num">3.2000</td><td class="num">3.9000</td><td class="num">7.5000</td><td class="num">185</td><td class="num">660000</td><td class="num">3.4200</td><td class="num">650000</td><td class="num">1350</td><td class="num">3800</td><td class="num">98</td><td class="num">2400</td><td class="num">115</td><td class="num">0.0300</td><td class="num">1950</td><td class="num">103.50</td><td class="num">105.50</td><td class="num">2760</td><td class="num">65.2000</td><td class="num">80</td><td class="num">99.5000</td><td class="num">82</td><td class="num">68</td><td class="num">110</td><td class="num">-3.3000</td><td class="num">0.8000</td><td class="num">33.5000</td><td class="num">30</td><td class="num">82</td><td class="num">101.50</td><td class="num">183</td><td class="num">27256</td></tr>
<tr><td></td><td class="num">2025</td><td class="num">112</td><td class="num">350</td><td class="num">3.6000</td><td class="num">1.8000</td><td class="num">3</td><td class="num">2.7500</td><td class="num">3</td><td class="num">1.8000</td><td class="num">99.8000</td><td class="num">2.8000</td><td class="num">3.5000</td><td class="num">6.8000</td><td class="num">184</td><td class="num">650000</td><td class="num">3.1000</td><td class="num">640000</td><td class="num">1380</td><td class="num">3900</td><td class="num">99</td><td class="num">2500</td><td class="num">110</td><td class="num">0.0300</td><td class="num">1980</td><td class="num">104</td><td class="num">106</td><td class="num">2770</td><td class="num">65.5000</td><td class="num">75</td><td class="num">100</td><td class="num">84</td><td class="num">65</td><td class="num">111</td><td class="num">-2</td><td class="num">1</td><td class="num">33</td><td class="num">29.5000</td><td class="num">81</td><td class="num">101</td><td class="num">185</td><td class="num">-10000</td></tr>
</table>
</div>
<div class="sheet" id="Zt_추정">
<div class="sheet-title">📊 Zt_추정</div>
<table>
<tr><td rowspan="1" colspan="6" class="merged"> Zt 추정 (Belkin & Suchower 1998)</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="6" class="section"> 방법론: 관측 전이행렬 역산 → WLS → Zt</td></tr>
<tr><td></td><td>자산상관계수 (ρ)</td><td class="num">0.2000</td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="6" class="section"> ρ = 0.20 근거</td></tr>
<tr><td></td><td>[1] Basel III IRB: 기업 ρ = 0.12~0.24 (CRE31.6)</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td> R = 0.12×(1-e^(-50×PD))/(1-e^(-50)) + 0.24×(1-(1-e^(-50×PD))/(1-e^(-50)))</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>[2] BBB(PD≈0.2%) → R=0.208, A(PD≈0.07%) → R=0.217</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>[3] 한국 기업 포트폴리오 평균: ρ ≈ 0.20 (투자/투기 혼합)</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>[4] Moody's Analytics CreditEdge: single-factor ρ ≈ 0.15~0.25</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>Zt 평균 (μ)</td><td class="num">-0.2001</td><td></td><td></td><td></td></tr>
<tr><td></td><td>Zt 표준편차 (σ)</td><td class="num">0.7673</td><td></td><td></td><td></td></tr>
<tr><td></td><td>관측 기간</td><td>1998~2025 (28개년)</td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>연도</td><td>Zt</td><td>HOUSING_PRICE</td><td>CURRENT_ACCOUNT</td><td>CREDIT_SPREAD_LAG1</td></tr>
<tr><td></td><td class="num">1998</td><td class="num">-2.1505</td><td></td><td></td><td></td></tr>
<tr><td></td><td class="num">1999</td><td class="num">-0.8673</td><td></td><td></td><td></td></tr>
<tr><td></td><td class="num">2000</td><td class="num">0.0862</td><td class="num">55.2000</td><td class="num">123.50</td><td></td></tr>
<tr><td></td><td class="num">2001</td><td class="num">-0.7190</td><td class="num">56.8000</td><td class="num">80.3000</td><td class="num">2.5500</td></tr>
<tr><td></td><td class="num">2002</td><td class="num">-0.8970</td><td class="num">65.3000</td><td class="num">53.9000</td><td class="num">3.1500</td></tr>
<tr><td></td><td class="num">2003</td><td class="num">-0.0819</td><td class="num">71.5000</td><td class="num">119.50</td><td class="num">2.7300</td></tr>
<tr><td></td><td class="num">2004</td><td class="num">-1.3041</td><td class="num">71</td><td class="num">284.20</td><td class="num">3.2700</td></tr>
<tr><td></td><td class="num">2005</td><td class="num">-0.3216</td><td class="num">73.5000</td><td class="num">149.80</td><td class="num">2.8100</td></tr>
<tr><td></td><td class="num">2006</td><td class="num">1.4361</td><td class="num">80.2000</td><td class="num">53.9000</td><td class="num">1.8300</td></tr>
<tr><td></td><td class="num">2007</td><td class="num">0.4562</td><td class="num">83.5000</td><td class="num">59.5000</td><td class="num">1.8300</td></tr>
<tr><td></td><td class="num">2008</td><td class="num">0.4385</td><td class="num">84</td><td class="num">-57.8000</td><td class="num">1.7400</td></tr>
<tr><td></td><td class="num">2009</td><td class="num">-0.6293</td><td class="num">84.8000</td><td class="num">328.10</td><td class="num">3.7100</td></tr>
<tr><td></td><td class="num">2010</td><td class="num">-1.0651</td><td class="num">87</td><td class="num">282.10</td><td class="num">3.4400</td></tr>
<tr><td></td><td class="num">2011</td><td class="num">0.000684</td><td class="num">89.5000</td><td class="num">184.10</td><td class="num">3.3200</td></tr>
<tr><td></td><td class="num">2012</td><td class="num">-1.1979</td><td class="num">89</td><td class="num">508.40</td><td class="num">3.3400</td></tr>
<tr><td></td><td class="num">2013</td><td class="num">-0.4545</td><td class="num">88.8000</td><td class="num">812.10</td><td class="num">2.8000</td></tr>
<tr><td></td><td class="num">2014</td><td class="num">0.9161</td><td class="num">90.2000</td><td class="num">843.50</td><td class="num">2.6800</td></tr>
<tr><td></td><td class="num">2015</td><td class="num">-0.4494</td><td class="num">95</td><td class="num">1059.40</td><td class="num">2.2300</td></tr>
<tr><td></td><td class="num">2016</td><td class="num">-0.0127</td><td class="num">97.5000</td><td class="num">992.40</td><td class="num">2.4300</td></tr>
<tr><td></td><td class="num">2017</td><td class="num">0.4831</td><td class="num">100</td><td class="num">752.60</td><td class="num">2.7200</td></tr>
<tr><td></td><td class="num">2018</td><td class="num">0.6004</td><td class="num">102</td><td class="num">774.70</td><td class="num">2.5500</td></tr>
<tr><td></td><td class="num">2019</td><td class="num">-0.2213</td><td class="num">104.50</td><td class="num">597</td><td class="num">2.7400</td></tr>
<tr><td></td><td class="num">2020</td><td class="num">0.5464</td><td class="num">110</td><td class="num">752.80</td><td class="num">2.5900</td></tr>
<tr><td></td><td class="num">2021</td><td class="num">0.4557</td><td class="num">122</td><td class="num">883</td><td class="num">3.2200</td></tr>
<tr><td></td><td class="num">2022</td><td class="num">0.5197</td><td class="num">128</td><td class="num">258.30</td><td class="num">3.3800</td></tr>
<tr><td></td><td class="num">2023</td><td class="num">-0.9198</td><td class="num">118</td><td class="num">355.20</td><td class="num">3.9300</td></tr>
<tr><td></td><td class="num">2024</td><td class="num">0.1165</td><td class="num">115</td><td class="num">380</td><td class="num">4</td></tr>
<tr><td></td><td class="num">2025</td><td class="num">-0.3662</td><td class="num">112</td><td class="num">350</td><td class="num">3.6000</td></tr>
</table>
</div>
<div class="sheet" id="AR1_모형">
<div class="sheet-title">📊 AR1_모형</div>
<table>
<tr><td rowspan="1" colspan="7" class="merged"> AR(1) + Macro 회귀 모형</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="7" class="section"> Z(t) = c + φ·Z(t-1) + Σ βᵢ·Xᵢ_std(t) + ε(t)</td></tr>
<tr><td></td><td>※ 거시변수는 표준화(mean=0, std=1) 후 투입. β = '1σ 충격 → ΔZ'로 해석</td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="7" class="section"> 회귀 계수</td></tr>
<tr><td></td><td>변수</td><td>계수</td><td>표준오차</td><td>t값</td><td>p값</td><td>유의성</td></tr>
<tr><td></td><td>const</td><td class="num">-0.1781</td><td class="num">0.0911</td><td class="num">-1.9541</td><td class="num">0.0648</td><td>*</td></tr>
<tr><td></td><td>Z_lag1</td><td class="num">-0.3352</td><td class="num">0.1488</td><td class="num">-2.2529</td><td class="num">0.0357</td><td>**</td></tr>
<tr><td></td><td>HOUSING_PRICE</td><td class="num">0.6412</td><td class="num">0.1341</td><td class="num">4.7800</td><td class="num">0.000114</td><td>***</td></tr>
<tr><td></td><td>CURRENT_ACCOUNT</td><td class="num">-0.1771</td><td class="num">0.1069</td><td class="num">-1.6570</td><td class="num">0.1131</td><td></td></tr>
<tr><td></td><td>CREDIT_SPREAD_LAG1</td><td class="num">-0.6439</td><td class="num">0.1099</td><td class="num">-5.8605</td><td class="num">0.000010</td><td>***</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="7" class="section"> 모형 진단 통계</td></tr>
<tr><td></td><td></td><td class="num">0.6667</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>Adj. R²</td><td class="num">0.6000</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>F 통계량</td><td class="num">10.0014</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>F p-value</td><td class="num">0.000130</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>AIC</td><td class="num">34.9959</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>Durbin-Watson</td><td class="num">2.8731</td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="7" class="section"> 거시변수 표본 통계 (표준화 전 원시값)</td></tr>
<tr><td></td><td>변수</td><td>평균</td><td>표준편차</td><td>최근값</td><td></td><td></td></tr>
<tr><td></td><td>HOUSING_PRICE</td><td class="num">91.3192</td><td class="num">19.3527</td><td class="num">112</td><td></td><td></td></tr>
<tr><td></td><td>CURRENT_ACCOUNT</td><td class="num">422.33</td><td class="num">334.75</td><td class="num">350</td><td></td><td></td></tr>
<tr><td></td><td>CREDIT_SPREAD_LAG1</td><td class="num">2.9036</td><td class="num">0.6262</td><td class="num">3.6000</td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="7" class="section"> 변수별 경제적 해석</td></tr>
<tr><td></td><td>HOUSING_PRICE</td><td>β=+0.6412 (+)</td><td rowspan="1" colspan="4">주택가격↑ → 담보가치↑ → 차입여력↑ → 부도↓ → Z↑</td></tr>
<tr><td></td><td>CURRENT_ACCOUNT</td><td>β=-0.1771 ()</td><td rowspan="1" colspan="4">경상수지↑(흑자) → 불황기 수출의존↑ → Z↓ (한국 특수 패턴)</td></tr>
<tr><td></td><td>CREDIT_SPREAD_LAG1</td><td>β=-0.6439 ()</td><td rowspan="1" colspan="4">전년 스프레드↑ → 당해 신용위험 전이 → 부도↑ → Z↓ (시차 효과)</td></tr>
</table>
</div>
<div class="sheet" id="시나리오_Z경로">
<div class="sheet-title">📊 시나리오_Z경로</div>
<table>
<tr><td rowspan="1" colspan="5" class="merged"> 시나리오별 Z(t) 경로</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="5" class="section"> t=1: 거시 충격 적용 | t≥2: AR(1) 감쇠 → TTC 수렴</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>연도(t+k)</td><td>호황 (Upside)</td><td>중립 (Base)</td><td>불황 (Downside)</td></tr>
<tr><td></td><td class="num">1</td><td class="num">1.0527</td><td class="num">-0.0553</td><td class="num">-1.7174</td></tr>
<tr><td></td><td class="num">2</td><td class="num">-0.5309</td><td class="num">-0.1595</td><td class="num">0.3975</td></tr>
<tr><td></td><td class="num">3</td><td class="num">-0.000133</td><td class="num">-0.1246</td><td class="num">-0.3113</td></tr>
<tr><td></td><td class="num">4</td><td class="num">-0.1781</td><td class="num">-0.1363</td><td class="num">-0.0737</td></tr>
<tr><td></td><td class="num">5</td><td class="num">-0.1184</td><td class="num">-0.1324</td><td class="num">-0.1534</td></tr>
<tr><td></td><td class="num">6</td><td class="num">-0.1384</td><td class="num">-0.1337</td><td class="num">-0.1267</td></tr>
<tr><td></td><td class="num">7</td><td class="num">-0.1317</td><td class="num">-0.1333</td><td class="num">-0.1356</td></tr>
<tr><td></td><td class="num">8</td><td class="num">-0.1340</td><td class="num">-0.1334</td><td class="num">-0.1326</td></tr>
<tr><td></td><td class="num">9</td><td class="num">-0.1332</td><td class="num">-0.1334</td><td class="num">-0.1336</td></tr>
<tr><td></td><td class="num">10</td><td class="num">-0.1335</td><td class="num">-0.1334</td><td class="num">-0.1333</td></tr>
<tr><td></td><td class="num">15</td><td class="num">-0.1334</td><td class="num">-0.1334</td><td class="num">-0.1334</td></tr>
<tr><td></td><td class="num">20</td><td class="num">-0.1334</td><td class="num">-0.1334</td><td class="num">-0.1334</td></tr>
<tr><td></td><td class="num">25</td><td class="num">-0.1334</td><td class="num">-0.1334</td><td class="num">-0.1334</td></tr>
<tr><td></td><td class="num">30</td><td class="num">-0.1334</td><td class="num">-0.1334</td><td class="num">-0.1334</td></tr>
<tr><td></td><td class="num">40</td><td class="num">-0.1334</td><td class="num">-0.1334</td><td class="num">-0.1334</td></tr>
<tr><td></td><td class="num">50</td><td class="num">-0.1334</td><td class="num">-0.1334</td><td class="num">-0.1334</td></tr>
</table>
</div>
<div class="sheet" id="Lifetime_PD">
<div class="sheet-title">📊 Lifetime_PD</div>
<table>
<tr><td rowspan="1" colspan="10" class="merged"> 시나리오별 누적 Lifetime PD (%)</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 호황 (Upside) (가중치 20%)</td></tr>
<tr><td></td><td>시나리오</td><td>연도</td><td>AAA</td><td>AA</td><td>A</td><td>BBB</td><td>BB</td><td>B</td><td>CCC</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">1</td><td class="num">0.001305</td><td class="num">0.002038</td><td class="num">0.007820</td><td class="num">0.1078</td><td class="num">0.8244</td><td class="num">2.5111</td><td class="num">11.0291</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">2</td><td class="num">0.0334</td><td class="num">0.0489</td><td class="num">0.1523</td><td class="num">1.2209</td><td class="num">6.1478</td><td class="num">14.3028</td><td class="num">30.5701</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">3</td><td class="num">0.0454</td><td class="num">0.0712</td><td class="num">0.2728</td><td class="num">2.0723</td><td class="num">9.7869</td><td class="num">21.5086</td><td class="num">39.4917</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">5</td><td class="num">0.0774</td><td class="num">0.1449</td><td class="num">0.7004</td><td class="num">4.5770</td><td class="num">18.5657</td><td class="num">35.8879</td><td class="num">52.8646</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">7</td><td class="num">0.1092</td><td class="num">0.2499</td><td class="num">1.3212</td><td class="num">7.5916</td><td class="num">27.1785</td><td class="num">47.4539</td><td class="num">62.0428</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">10</td><td class="num">0.1584</td><td class="num">0.4910</td><td class="num">2.6113</td><td class="num">12.7192</td><td class="num">39.0549</td><td class="num">60.7263</td><td class="num">72.0198</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">15</td><td class="num">0.2491</td><td class="num">1.1786</td><td class="num">5.5874</td><td class="num">21.7816</td><td class="num">55.0271</td><td class="num">75.2884</td><td class="num">82.7164</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">20</td><td class="num">0.3584</td><td class="num">2.2744</td><td class="num">9.2994</td><td class="num">30.3966</td><td class="num">66.4407</td><td class="num">83.9785</td><td class="num">89.0086</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">30</td><td class="num">0.6701</td><td class="num">5.6406</td><td class="num">17.6391</td><td class="num">44.4615</td><td class="num">79.7891</td><td class="num">92.5201</td><td class="num">95.0957</td></tr>
<tr><td></td><td>호황 (Upside)</td><td class="num">50</td><td class="num">1.8493</td><td class="num">15.2686</td><td class="num">33.0938</td><td class="num">61.2940</td><td class="num">89.5458</td><td class="num">97.3332</td><td class="num">98.4190</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 중립 (Base) (가중치 50%)</td></tr>
<tr><td></td><td>시나리오</td><td>연도</td><td>AAA</td><td>AA</td><td>A</td><td>BBB</td><td>BB</td><td>B</td><td>CCC</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">1</td><td class="num">0.0131</td><td class="num">0.0193</td><td class="num">0.0626</td><td class="num">0.5973</td><td class="num">3.2599</td><td class="num">8.0151</td><td class="num">25.1118</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">2</td><td class="num">0.0291</td><td class="num">0.0456</td><td class="num">0.1786</td><td class="num">1.5138</td><td class="num">7.4367</td><td class="num">16.7838</td><td class="num">40.3810</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">3</td><td class="num">0.0443</td><td class="num">0.0746</td><td class="num">0.3382</td><td class="num">2.6152</td><td class="num">11.7983</td><td class="num">24.7793</td><td class="num">49.7632</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">5</td><td class="num">0.0755</td><td class="num">0.1523</td><td class="num">0.8153</td><td class="num">5.3791</td><td class="num">20.9219</td><td class="num">38.8003</td><td class="num">61.4840</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">7</td><td class="num">0.1073</td><td class="num">0.2648</td><td class="num">1.5026</td><td class="num">8.6767</td><td class="num">29.8387</td><td class="num">50.1129</td><td class="num">69.1802</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">10</td><td class="num">0.1567</td><td class="num">0.5216</td><td class="num">2.9118</td><td class="num">14.2067</td><td class="num">41.9948</td><td class="num">63.0198</td><td class="num">77.3817</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">15</td><td class="num">0.2480</td><td class="num">1.2463</td><td class="num">6.1132</td><td class="num">23.8192</td><td class="num">58.1023</td><td class="num">77.0581</td><td class="num">86.1044</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">20</td><td class="num">0.3583</td><td class="num">2.3902</td><td class="num">10.0534</td><td class="num">32.8144</td><td class="num">69.4296</td><td class="num">85.3543</td><td class="num">91.2138</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">30</td><td class="num">0.6727</td><td class="num">5.8734</td><td class="num">18.7773</td><td class="num">47.2255</td><td class="num">82.3953</td><td class="num">93.4112</td><td class="num">96.1343</td></tr>
<tr><td></td><td>중립 (Base)</td><td class="num">50</td><td class="num">1.8588</td><td class="num">15.7497</td><td class="num">34.6026</td><td class="num">63.9444</td><td class="num">91.4614</td><td class="num">97.8386</td><td class="num">98.7959</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="10" class="section"> 불황 (Downside) (가중치 30%)</td></tr>
<tr><td></td><td>시나리오</td><td>연도</td><td>AAA</td><td>AA</td><td>A</td><td>BBB</td><td>BB</td><td>B</td><td>CCC</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">1</td><td class="num">0.2401</td><td class="num">0.3283</td><td class="num">0.8295</td><td class="num">4.6213</td><td class="num">15.5554</td><td class="num">28.3305</td><td class="num">56.3571</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">2</td><td class="num">0.2463</td><td class="num">0.3471</td><td class="num">1.0083</td><td class="num">5.5513</td><td class="num">18.4195</td><td class="num">33.1617</td><td class="num">63.4497</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">3</td><td class="num">0.2689</td><td class="num">0.4089</td><td class="num">1.4462</td><td class="num">7.5656</td><td class="num">23.7982</td><td class="num">40.9283</td><td class="num">70.4479</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">5</td><td class="num">0.3005</td><td class="num">0.5393</td><td class="num">2.3388</td><td class="num">11.2799</td><td class="num">32.6050</td><td class="num">52.0021</td><td class="num">77.5624</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">7</td><td class="num">0.3341</td><td class="num">0.7284</td><td class="num">3.4487</td><td class="num">15.3342</td><td class="num">40.9153</td><td class="num">60.9306</td><td class="num">82.1168</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">10</td><td class="num">0.3884</td><td class="num">1.1346</td><td class="num">5.4544</td><td class="num">21.6124</td><td class="num">51.8620</td><td class="num">71.0807</td><td class="num">86.9009</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">15</td><td class="num">0.4951</td><td class="num">2.1663</td><td class="num">9.4855</td><td class="num">31.7065</td><td class="num">65.8925</td><td class="num">82.0951</td><td class="num">91.9642</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">20</td><td class="num">0.6316</td><td class="num">3.6412</td><td class="num">14.0208</td><td class="num">40.6129</td><td class="num">75.5089</td><td class="num">88.5935</td><td class="num">94.9255</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">30</td><td class="num">1.0301</td><td class="num">7.7128</td><td class="num">23.3321</td><td class="num">54.2014</td><td class="num">86.2731</td><td class="num">94.8930</td><td class="num">97.7739</td></tr>
<tr><td></td><td>불황 (Downside)</td><td class="num">50</td><td class="num">2.4554</td><td class="num">18.1998</td><td class="num">39.0619</td><td class="num">69.2061</td><td class="num">93.5729</td><td class="num">98.3429</td><td class="num">99.3113</td></tr>
</table>
</div>
<div class="sheet" id="가중평균_PD">
<div class="sheet-title">📊 가중평균_PD</div>
<table>
<tr><td rowspan="1" colspan="9" class="merged"> 확률가중 Lifetime PD (%)</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td rowspan="1" colspan="9" class="section"> PD_weighted(t) = Σ w_s × PD_s(t)</td></tr>
<tr><td></td><td>= 20%×호황 (Upside) + 50%×중립 (Base) + 30%×불황 (Downside)</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>연도</td><td>AAA</td><td>AA</td><td>A</td><td>BBB</td><td>BB</td><td>B</td><td>CCC</td></tr>
<tr><td></td><td class="num">1</td><td class="num">0.0788</td><td class="num">0.1085</td><td class="num">0.2817</td><td class="num">1.7066</td><td class="num">6.4614</td><td class="num">13.0089</td><td class="num">31.6688</td></tr>
<tr><td></td><td class="num">2</td><td class="num">0.0952</td><td class="num">0.1367</td><td class="num">0.4223</td><td class="num">2.6665</td><td class="num">10.4737</td><td class="num">21.2010</td><td class="num">45.3394</td></tr>
<tr><td></td><td class="num">3</td><td class="num">0.1119</td><td class="num">0.1742</td><td class="num">0.6575</td><td class="num">3.9917</td><td class="num">14.9960</td><td class="num">28.9699</td><td class="num">53.9143</td></tr>
<tr><td></td><td class="num">5</td><td class="num">0.1434</td><td class="num">0.2669</td><td class="num">1.2494</td><td class="num">6.9889</td><td class="num">23.9556</td><td class="num">42.1784</td><td class="num">64.5837</td></tr>
<tr><td></td><td class="num">7</td><td class="num">0.1757</td><td class="num">0.4009</td><td class="num">2.0501</td><td class="num">10.4570</td><td class="num">32.6296</td><td class="num">52.8264</td><td class="num">71.6337</td></tr>
<tr><td></td><td class="num">10</td><td class="num">0.2265</td><td class="num">0.6994</td><td class="num">3.6145</td><td class="num">16.1309</td><td class="num">44.3670</td><td class="num">64.9794</td><td class="num">79.1651</td></tr>
<tr><td></td><td class="num">15</td><td class="num">0.3224</td><td class="num">1.5088</td><td class="num">7.0197</td><td class="num">25.7778</td><td class="num">59.8243</td><td class="num">78.2153</td><td class="num">87.1847</td></tr>
<tr><td></td><td class="num">20</td><td class="num">0.4403</td><td class="num">2.7423</td><td class="num">11.0928</td><td class="num">34.6704</td><td class="num">70.6556</td><td class="num">86.0509</td><td class="num">91.8863</td></tr>
<tr><td></td><td class="num">30</td><td class="num">0.7794</td><td class="num">6.3787</td><td class="num">19.9161</td><td class="num">48.7655</td><td class="num">83.0374</td><td class="num">93.6775</td><td class="num">96.4184</td></tr>
<tr><td></td><td class="num">50</td><td class="num">2.0359</td><td class="num">16.3885</td><td class="num">35.6386</td><td class="num">64.9928</td><td class="num">91.7117</td><td class="num">97.8888</td><td class="num">98.8751</td></tr>
</table>
</div>
<div class="sheet" id="검증결과">
<div class="sheet-title">📊 검증결과</div>
<table>
<tr><td rowspan="1" colspan="7" class="merged"> 통계적 검증 결과</td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td>검정</td><td>대상</td><td>통계량</td><td>p-value</td><td>결과</td><td>해석</td></tr>
<tr><td></td><td>ADF (Augmented Dickey-Fuller)</td><td>Zt 시계열</td><td>-4.8749</td><td>0.0000</td><td class="pass">Pass O</td><td>정상 시계열 (p=0.0000, α=0.05)</td></tr>
<tr><td></td><td>Shapiro-Wilk Normality Test</td><td>Zt 시계열</td><td>0.9818</td><td>0.8921</td><td class="pass">Pass O</td><td>정규분포 (p=0.8921, α=0.05)</td></tr>
<tr><td></td><td>Ljung-Box Q-test</td><td>잔차 자기상관</td><td>12.2306</td><td>0.0318</td><td class="fail">Fail X</td><td>자기상관 존재 (DW=2.873, LB p=0.0318)</td></tr>
<tr><td></td><td>Breusch-Pagan / ARCH-LM</td><td>잔차 이분산</td><td>BP=3.3425</td><td>0.5022</td><td class="pass">Pass O</td><td>등분산 (BP p=0.5022, ARCH p=0.9545)</td></tr>
<tr><td></td><td>R² / F-test</td><td>모형 설명력</td><td>R²=0.6667</td><td>0.0001</td><td class="pass">Pass O</td><td>R²=0.667, Adj.R²=0.600</td></tr>
<tr><td></td><td>PD Properties</td><td>Cumulative PD (upside)</td><td>-</td><td>-</td><td class="pass">Pass O</td><td>모든 검증 통과</td></tr>
<tr><td></td><td>PD Properties</td><td>Cumulative PD (base)</td><td>-</td><td>-</td><td class="pass">Pass O</td><td>모든 검증 통과</td></tr>
<tr><td></td><td>PD Properties</td><td>Cumulative PD (downside)</td><td>-</td><td>-</td><td class="pass">Pass O</td><td>모든 검증 통과</td></tr>
</table>
</div>
</body></html>

Binary file not shown.

View File

@@ -58,14 +58,17 @@ class ScenarioEngine:
"""
시나리오별 Zt 경로 생성 (50년)
AR(1) 모형이 있으면: forecast_z_path() 사용 (macro_shocks 기반)
없으면: Z-직접 방식 (μ±kσ) fallback
Parameters
----------
zt_history : Dict[int, float]
과거 Zt 시계열
macro_model : MacroZtModel, optional
거시→Zt 회귀모형
거시→Zt 회귀모형 (AR(1) 또는 OLS)
macro_scenarios : Dict[str, pd.DataFrame], optional
시나리오별 거시변수 경로
(OLS 모형용) 시나리오별 거시변수 경로
base_year : int
기준 연도
@@ -77,55 +80,83 @@ class ScenarioEngine:
zt_values = np.array(list(zt_history.values()))
z_mean = zt_values.mean()
z_std = zt_values.std()
z_last = zt_values[-1] # 마지막 관측값
logger.info(f"Zt 통계: μ={z_mean:.4f}, σ={z_std:.4f}")
logger.info(f"Zt 통계: μ={z_mean:.4f}, σ={z_std:.4f}, Z_last={z_last:.4f}")
z_paths = {}
for scenario_name, scenario_cfg in self.scenario_config.items():
z_multiplier = scenario_cfg.get("z_multiplier", 0.0)
# AR(1) 모형 사용 경로
use_ar1 = (macro_model is not None and
hasattr(macro_model, 'is_ar1') and
macro_model.is_ar1)
if use_ar1:
logger.info("AR(1)+Macro 모형으로 시나리오 경로 생성")
# 시나리오별 초기 Z 수준
z_scenario = z_mean + z_multiplier * z_std
for scenario_name, scenario_cfg in self.scenario_config.items():
# config에서 macro_shocks 가져오기
macro_shocks = scenario_cfg.get("macro_shocks", {})
# forecast_z_path로 50년 경로 생성
z_path = macro_model.forecast_z_path(
z_last=z_last,
macro_shocks=macro_shocks,
horizon=self.total_horizon
)
z_paths[scenario_name] = z_path
logger.info(
f" {scenario_name}: Z[1]={z_path[0]:+.3f}, "
f"Z[5]={z_path[4]:+.3f}, Z[10]={z_path[9]:+.3f}, "
f"Z[50]={z_path[-1]:+.3f}"
)
else:
# Fallback: Z-직접 방식
logger.info("Z-직접 방식으로 시나리오 경로 생성 (AR(1) 없음)")
# 거시 모형이 있으면 단기(1-5년) 거시 기반 Zt 예측
if macro_model is not None and macro_scenarios is not None:
scenario_key = scenario_name
if scenario_key in macro_scenarios:
macro_path = macro_scenarios[scenario_key]
z_short = macro_model.predict(macro_path)
n_short = min(len(z_short), self.pit_horizon)
for scenario_name, scenario_cfg in self.scenario_config.items():
z_multiplier = scenario_cfg.get("z_multiplier", 0.0)
z_scenario = z_mean + z_multiplier * z_std
# OLS 거시 모형이 있으면 단기 거시 기반
if macro_model is not None and macro_scenarios is not None:
scenario_key = scenario_name
if scenario_key in macro_scenarios:
macro_path = macro_scenarios[scenario_key]
z_short = macro_model.predict(macro_path)
n_short = min(len(z_short), self.pit_horizon)
else:
z_short = np.full(self.pit_horizon, z_scenario)
n_short = self.pit_horizon
else:
z_short = np.full(self.pit_horizon, z_scenario)
n_short = self.pit_horizon
else:
z_short = np.full(self.pit_horizon, z_scenario)
n_short = self.pit_horizon
# 전체 50년 Zt 경로 구성
z_path = np.zeros(self.total_horizon)
# Phase 1: PIT 기간 (1~pit_horizon년)
for t in range(min(n_short, self.total_horizon)):
val = z_short[t] if t < len(z_short) else z_scenario
z_path[t] = val if np.isfinite(val) else z_scenario
# Phase 2: Mean-reversion 기간 (pit_horizon+1 ~ transition_horizon년)
for t in range(self.pit_horizon, min(self.transition_horizon, self.total_horizon)):
decay = np.exp(-self.mean_reversion_lambda * (t - self.pit_horizon + 1))
z_path[t] = z_path[self.pit_horizon - 1] * decay
# Phase 3: TTC 기간 (transition_horizon+1 ~ total_horizon년)
for t in range(self.transition_horizon, self.total_horizon):
z_path[t] = 0.0 # TTC (Z=0)
z_paths[scenario_name] = z_path
logger.info(
f" {scenario_name}: Z[1]={z_path[0]:+.3f}, "
f"Z[5]={z_path[4]:+.3f}, Z[10]={z_path[9]:+.3f}, "
f"Z[50]={z_path[-1]:+.3f}"
)
z_path = np.zeros(self.total_horizon)
# Phase 1: PIT 기간
for t in range(min(n_short, self.total_horizon)):
val = z_short[t] if t < len(z_short) else z_scenario
z_path[t] = val if np.isfinite(val) else z_scenario
# Phase 2: Mean-reversion
for t in range(self.pit_horizon, min(self.transition_horizon, self.total_horizon)):
decay = np.exp(-self.mean_reversion_lambda * (t - self.pit_horizon + 1))
z_path[t] = z_path[self.pit_horizon - 1] * decay
# Phase 3: TTC
for t in range(self.transition_horizon, self.total_horizon):
z_path[t] = 0.0
z_paths[scenario_name] = z_path
logger.info(
f" {scenario_name}: Z[1]={z_path[0]:+.3f}, "
f"Z[5]={z_path[4]:+.3f}, Z[10]={z_path[9]:+.3f}, "
f"Z[50]={z_path[-1]:+.3f}"
)
return z_paths

316
tmp_analysis.py Normal file
View File

@@ -0,0 +1,316 @@
"""
================================================================================
확률 가중평균 시나리오 가중치 실증 분석
================================================================================
[목적]
IFRS 9 ECL 산출 시 복수 시나리오(호황/중립/불황)의 확률 가중치를 실증 데이터
기반으로 도출한다. 기존 하드코딩(20/50/30)을 대체할 객관적 가중치를 산출하고,
그 적정성을 통계적으로 검증한다.
[방법론 개요]
1. 시나리오 비율 선정: Downside 30% / Baseline 50% / Upside 20%
(1) 30/50/20 비율의 근거
- IFRS 9은 ECL을 "편향 없는(unbiased) 확률가중 금액"으로 측정하도록
요구하며(5.5.17), 복수 시나리오의 확률가중을 통해 산출한다.
- IFRS 9은 특정 시나리오 개수나 가중치 비율을 강제하지 않으므로,
각 기관이 자체 근거를 문서화하여 결정해야 한다.
- 30/50/20 분할은 다음 논리에 기반한다:
a) Downside에 Upside보다 더 큰 비중(30% > 20%)을 배분하는 것은
신용 손실 함수의 비선형성(convexity)을 반영한다.
-> PD는 경기 악화 시 급증하고 호황 시에는 완만히 감소한다.
-> 따라서 Downside 시나리오의 ECL 기여분이 Upside보다 크므로,
대칭 가중(25/50/25)보다 불황 쪽에 더 큰 비중을 두는 것이
보수적 추정(prudent estimation) 원칙에 부합한다.
b) Baseline이 50%로 가장 큰 비중을 차지하는 것은,
"가장 가능성 높은 시나리오"에 절반의 확률을 배정하는 것으로
합리적인 중심 시나리오 설정이다.
c) 불황(30%)과 호황(20%)의 합이 50%로, 양방향 테일을 균형있게
반영하되 하방 리스크에 더 무게를 둔다.
(2) 분위수에서의 의미
- 30/50/20은 연속 확률분포상의 영역 분할으로 해석할 수 있다:
Downside: 0 ~ 30th percentile (하위 30%)
Baseline: 30th ~ 80th percentile (중간 50%)
Upside: 80th ~ 100th percentile (상위 20%)
- 30th percentile과 80th percentile이 경계가 된다.
2. 가상 Zt(Virtual Zt) 구성
(1) 월간 거시경제변수 3종의 12개월 롤링 연환산:
- 원유(WTI): log(price) -- 수준(level) 변수
- 순상품교역조건지수: 12개월 수익률 (T(t)/T(t-12) - 1)
- KOSPI 평균: 12개월 log 수익률 (ln(T(t)/T(t-12)))
(2) 12개월 롤링으로 변환하는 이유:
- AR(1) 거시연계 모형의 beta 계수는 연간 데이터에서 추정되었으므로,
동일한 단위(연간 수익률)로 변환해야 beta를 적용할 수 있다.
- 월간 데이터를 그대로 사용하면 주기와 스케일이 맞지 않아
beta 적용이 불가능하다.
(3) beta 계수 적용:
- 연간 Zt ~ 거시변수 회귀에서 추정된 beta를 사용한다.
- Virtual_Zt(t) = const + b1 * X1(t) + b2 * X2(t) + b3 * X3(t)
- 이렇게 하면 월별로 하나의 Virtual Zt값이 생성된다.
- 300개월의 Virtual Zt -> 시나리오 비중 분석에 충분한 표본.
3. 정규분포 기반 확률 가중치 산출
(1) 경험적 분위수로 경계값 산출:
- q30 = Virtual Zt의 30th percentile 값
- q80 = Virtual Zt의 80th percentile 값
- 이 경계값은 30/50/20 분할에서 유래한다.
(2) Virtual Zt에 정규분포 N(mu, sigma)를 피팅:
- 정규분포 가정 하에, 경험적 분위수 경계의 "이론적" 면적을 산출한다.
- 데이터가 완벽한 정규분포이면 면적은 정확히 30/50/20이 된다.
- 실제 데이터의 비대칭성(skewness)이나 꼬리 두께(kurtosis)로 인해
경험적 분위수의 위치가 정규분포상의 이론적 위치와 다르면,
면적이 30/50/20에서 이탈한다.
- 이 이탈(gap)이 "실증 데이터 기반 시나리오 가중치"이다.
(3) 정규분포 가정의 적정성:
- Jarque-Bera 정규성 검정으로 가정의 유효성을 확인한다.
- 기각되지 않으면(p > 0.05) 정규분포 기반 면적을 가중치로 사용한다.
- 기각되면 경험적 빈도(= 정의상 30/50/20)를 유지하거나,
비모수적 방법(KDE 등)을 검토한다.
4. 결과 해석
- 정규분포 면적이 30/50/20에서 이탈한 정도가 곧
"실측 데이터에 기반한 보정된 시나리오 가중치"이다.
- 예: 정규분포 면적이 27/56/17이면, 실제 데이터의 분포 특성상
하드코딩 30/50/20보다 중립 비중이 높고 테일 비중이 낮다는 의미이다.
- 이 가중치는 분기/연간으로 갱신 가능하며,
감사(audit) 시 "과거 N개월 거시데이터의 분포에서 도출"이라는
정량적 근거를 제시할 수 있다.
================================================================================
"""
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
import numpy as np
from scipy.stats import norm, jarque_bera
def analyze_scenario_weights(
data_lists,
betas,
const,
var_names=None,
pct_down=30,
pct_up=80,
):
"""
확률 가중평균 시나리오 가중치 실증 분석
Parameters
----------
data_lists : list of array-like
3개 거시변수의 변환된 시계열 데이터 (동일 길이).
예: [oil_log, tot_ann_ret, kospi_ann_logret]
각 리스트는 12개월 롤링 연환산 등 이미 변환 완료된 상태여야 한다.
betas : list of float
3개 거시변수의 회귀 계수.
예: [-0.8037, -10.8856, 3.2878]
const : float
회귀 상수항.
예: 2.8575
var_names : list of str, optional
변수 이름 (출력용). 기본값: ["X1", "X2", "X3"]
pct_down : float
Downside/Baseline 경계 분위수 (기본: 30)
pct_up : float
Baseline/Upside 경계 분위수 (기본: 80)
Returns
-------
dict
분석 결과를 담은 딕셔너리:
- 'virtual_zt': Virtual Zt 배열
- 'weights_empirical': 경험적 가중치 {'down': ..., 'base': ..., 'up': ...}
- 'weights_normal': 정규분포 면적 기반 가중치
- 'scenario_zt': 시나리오별 평균 Zt
- 'normality_pvalue': Jarque-Bera p-value
- 'q_boundaries': (q_low, q_high) 분위수 경계값
"""
if var_names is None:
var_names = ["X1", "X2", "X3"]
# ========================================================================
# 1. Virtual Zt 생성
# - beta 계수를 각 변수에 적용하여 월별 가상 Zt를 산출
# ========================================================================
X = np.column_stack(data_lists)
beta_arr = np.array(betas)
Z_v = const + X @ beta_arr
n = len(Z_v)
print(f'=== Virtual Zt ({n} observations) ===')
print(f'mu = {Z_v.mean():.4f}')
print(f'sigma = {Z_v.std():.4f}')
print(f'min = {Z_v.min():.4f}')
print(f'max = {Z_v.max():.4f}')
print()
# Sanity check: top/bottom 5
sorted_idx = np.argsort(Z_v)
print('Bottom 5 (recession):')
for i in sorted_idx[:5]:
parts = ' '.join(f'{var_names[j]}={X[i,j]:.4f}' for j in range(3))
print(f' [{i:3d}] Zt={Z_v[i]:+.4f} {parts}')
print('Top 5 (boom):')
for i in sorted_idx[-5:][::-1]:
parts = ' '.join(f'{var_names[j]}={X[i,j]:.4f}' for j in range(3))
print(f' [{i:3d}] Zt={Z_v[i]:+.4f} {parts}')
print()
# ========================================================================
# 2. 분위수 경계 설정
# ========================================================================
q_low = np.percentile(Z_v, pct_down)
q_high = np.percentile(Z_v, pct_up)
pct_base = pct_up - pct_down
pct_up_width = 100 - pct_up
print(f'=== Percentile boundaries ({pct_down}/{pct_base}/{pct_up_width}) ===')
print(f'q{pct_down} = {q_low:.4f} (downside/baseline)')
print(f'q{pct_up} = {q_high:.4f} (baseline/upside)')
print()
# ========================================================================
# 3. 경험적 빈도 (정의상 pct_down / pct_base / pct_up_width)
# ========================================================================
w_d = np.mean(Z_v < q_low)
w_m = np.mean((Z_v >= q_low) & (Z_v <= q_high))
w_u = np.mean(Z_v > q_high)
print(f'=== Empirical frequency (sanity check) ===')
print(f'Downside: {w_d*100:.1f}%')
print(f'Baseline: {w_m*100:.1f}%')
print(f'Upside: {w_u*100:.1f}%')
print()
# ========================================================================
# 4. 정규분포 피팅 -> 면적 산출 (실증 가중치)
# ========================================================================
mu_z = Z_v.mean()
sig_z = Z_v.std()
p_d = norm.cdf(q_low, mu_z, sig_z)
p_u = 1 - norm.cdf(q_high, mu_z, sig_z)
p_m = 1 - p_d - p_u
print(f'=== Normal N({mu_z:.4f}, {sig_z:.4f}) area ===')
print(f'Downside (Z < {q_low:.4f}): {p_d*100:.2f}%')
print(f'Baseline ({q_low:.4f} ~ {q_high:.4f}): {p_m*100:.2f}%')
print(f'Upside (Z > {q_high:.4f}): {p_u*100:.2f}%')
print()
print(f'=== KEY RESULT ===')
print(f'Downside: empirical {pct_down:.0f}% vs normal {p_d*100:.2f}%')
print(f'Baseline: empirical {pct_base:.0f}% vs normal {p_m*100:.2f}%')
print(f'Upside: empirical {pct_up_width:.0f}% vs normal {p_u*100:.2f}%')
print()
# ========================================================================
# 5. 시나리오별 대표 Zt
# ========================================================================
z_down = Z_v[Z_v < q_low].mean()
z_base = Z_v[(Z_v >= q_low) & (Z_v <= q_high)].mean()
z_up = Z_v[Z_v > q_high].mean()
print(f'=== Scenario Zt levels ===')
print(f'Downside avg Zt = {z_down:.4f}')
print(f'Baseline avg Zt = {z_base:.4f}')
print(f'Upside avg Zt = {z_up:.4f}')
print()
# ========================================================================
# 6. 정규성 검정 (Jarque-Bera)
# ========================================================================
jb_stat, jb_p = jarque_bera(Z_v)
print(f'=== Normality test (Jarque-Bera) ===')
print(f'JB stat = {jb_stat:.2f}, p-value = {jb_p:.4f}')
if jb_p < 0.05:
print('-> REJECT normality (p < 0.05) -> empirical quantile preferred')
else:
print('-> Cannot reject normality -> normal assumption OK')
print()
return {
'virtual_zt': Z_v,
'weights_empirical': {
'down': w_d, 'base': w_m, 'up': w_u
},
'weights_normal': {
'down': p_d, 'base': p_m, 'up': p_u
},
'scenario_zt': {
'down': z_down, 'base': z_base, 'up': z_up
},
'normality_pvalue': jb_p,
'q_boundaries': (q_low, q_high),
}
# ============================================================================
# 사용 예시
# ============================================================================
if __name__ == '__main__':
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
# --- 데이터 로딩 ---
df = pd.read_excel(
r'C:\Users\Certes\Downloads\ECOS데이터조회(월기준)_20260317_3개_수정본.xlsx',
sheet_name='데이터'
)
cols = df.columns.tolist()
item_col = cols[2]
month_cols = [c for c in cols if '/' in str(c)]
data = {}
for _, row in df.iterrows():
name = str(row[item_col]).strip()
vals = pd.Series(row[month_cols].values.astype(float), index=month_cols)
data[name] = vals
keys = list(data.keys())
oil = data[[k for k in keys if 'WTI' in k][0]]
tot = data[[k for k in keys if 'KOSPI' not in k and 'WTI' not in k][0]]
kospi = data[[k for k in keys if 'KOSPI' in k][0]]
# --- 12개월 롤링 연환산 변환 ---
oil_log = np.log(oil)
tot_ann_ret = tot / tot.shift(12) - 1
kospi_ann_logret = np.log(kospi / kospi.shift(12))
valid = oil_log.notna() & tot_ann_ret.notna() & kospi_ann_logret.notna()
months = np.array(month_cols)[valid.values]
print(f'Period: {months[0]} ~ {months[-1]} ({len(months)} months)')
print()
# --- 분석 실행 ---
result = analyze_scenario_weights(
data_lists=[
oil_log[valid].values,
tot_ann_ret[valid].values,
kospi_ann_logret[valid].values,
],
betas=[-0.8037, -10.8856, 3.2878],
const=2.8575,
var_names=['oil_log', 'tot_ret', 'kospi_logret'],
pct_down=30,
pct_up=80,
)

View File

@@ -44,7 +44,11 @@ def test_stationarity(
-------
dict with test_statistic, p_value, critical_values, is_stationary
"""
result = adfuller(series, autolag="AIC")
# BIC를 사용하는 이유:
# - AIC는 소표본(N<50)에서 과다 lag 선택 경향 (Hamilton 1994, Ch.17)
# - N=26에서 AIC → lag=8 → 유효관측치=17 → 검정력 상실
# - BIC는 보수적 lag 선택 → 소표본에서 적절 (Schwarz 1978)
result = adfuller(series, autolag="BIC")
is_stationary = result[1] < significance