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
This commit is contained in:
Variet Agent
2026-03-12 00:06:23 +09:00
parent 87725b7c19
commit d1ddf06e5d
19 changed files with 1736 additions and 167 deletions

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"]