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:
102
data/pd_floor.py
102
data/pd_floor.py
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user