From 1b6af851ccaf0cb14f1d506e6249d2391f2b93f8 Mon Sep 17 00:00:00 2001 From: EDF Agent Date: Thu, 12 Mar 2026 00:23:59 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20known-issues=203=EA=B1=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20+=20devlog=202026-03-12=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/references/known-issues.md | 18 +++++++++ docs/devlog/2026-03-12.md | 7 ++++ test_setup.py | 61 +++--------------------------- 3 files changed, 31 insertions(+), 55 deletions(-) create mode 100644 docs/devlog/2026-03-12.md diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index 5fbba7b..c74311c 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -28,3 +28,21 @@ - **원인**: PowerShell의 `'`, `"`, `{}` 이스케이핑이 Python f-string과 충돌 - **해결**: 항상 별도 `.py` 파일로 작성하여 실행 - **주의**: PowerShell에서 복잡한 Python 코드를 `-c` 옵션으로 실행하지 말 것 + +### 2026-03-12 pykrx — get_market_cap 전 함수군 완전 불가 +- **증상**: `get_market_cap`, `get_market_cap_by_date`, `get_market_cap_by_ticker` 모두 0 반환 또는 columns 오류 +- **원인**: pykrx 1.2.4에서 시가총액/상장주식수 관련 API 전면 고장 +- **해결**: DART `total_equity`(자본총계)를 E(자기자본)로 대체 사용 +- **주의**: 시가총액 필요 시 유료 DB(FnGuide 등) 또는 DART 발행주식수 × 종가로 별도 산출 필요 + +### 2026-03-12 Merton — SPAC/스팩 종목 DD 비정상 (50~124) +- **증상**: DD가 50~124로 비정상적으로 높아 AAA 등급 오염 +- **원인**: SPAC은 자산=현금, 부채=극소 → Merton 모형에서 부도확률이 0에 수렴 +- **해결**: 종목명에 "스팩", "SPAC", "N호" 패턴 포함 시 제외 (62개) +- **주의**: 리츠(26개)도 동일 문제. 비정상 종목 필터 항상 적용 + +### 2026-03-12 Shadow Rating — EDF 등급역전 (composite score 기반) +- **증상**: AA-의 EDF가 A+보다 높은 역전 현상 +- **원인**: composite score에서 DD 외 재무비율(레버리지/ROA)이 등급을 왜곡 +- **해결**: DD 가중치를 50% → 70%로 상향, isotonic 단조보정 적용 +- **주의**: composite score 산출 시 DD 가중치를 70% 미만으로 낮추면 역전 재발 diff --git a/docs/devlog/2026-03-12.md b/docs/devlog/2026-03-12.md new file mode 100644 index 0000000..f090870 --- /dev/null +++ b/docs/devlog/2026-03-12.md @@ -0,0 +1,7 @@ +# Devlog — 2026-03-12 + +| # | 시간 | 작업 | 커밋 | 상태 | +|---|------|------|------|------| +| 001 | 23:31 | Merton DD/EDF 2,385종목 일괄 산출 + 금융업 274종목 필터 | `0547dfb` | ✅ | +| 002 | 23:45 | Shadow Rating + 등급별 부도율 블렌딩 (베이지안 보정) | `0c0b212` | ✅ | +| 003 | 00:15 | 데이터 재검증 (SPAC/리츠 제거, DD캡, EDF 단조보정) + Excel 리포트 | `864decd` | ✅ | diff --git a/test_setup.py b/test_setup.py index 2af75bf..8f48bdd 100644 --- a/test_setup.py +++ b/test_setup.py @@ -1,55 +1,6 @@ -"""등급역전 분석 + AAA EDF 진단""" -import sqlite3 -import numpy as np -from scipy.stats import norm - -conn = sqlite3.connect("data/edf.db") - -# 1) 등급별 EDF 상세 확인 — 역전 여부 -print("=== 등급별 EDF 상세 (역전 확인) ===") -rows = conn.execute(""" - SELECT dd_rating, COUNT(*), AVG(DD), MIN(DD), MAX(DD), AVG(EDF), MIN(EDF), MAX(EDF) - FROM merton_results - GROUP BY dd_rating - ORDER BY AVG(DD) DESC -""").fetchall() - -rating_order = ["AAA","AA+","AA","AA-","A+","A","A-","BBB+","BBB","BBB-", - "BB+","BB","BB-","B+","B","B-","CCC+","CCC","CCC-"] - -prev_edf = -1 -print(f"{'등급':>5} | {'N':>5} | {'DD평균':>8} | {'DD최소':>8} | {'EDF평균':>12} | {'역전':>4}") -print("-" * 65) -for rating in rating_order: - match = [r for r in rows if r[0] == rating] - if match: - r = match[0] - edf = r[5] - inversion = " !!!" if edf < prev_edf and prev_edf >= 0 else "" - print(f" {r[0]:5s} | {r[1]:5d} | {r[2]:8.2f} | {r[3]:8.2f} | {edf:12.8f} | {inversion}") - prev_edf = edf - -# 2) AAA 개별 확인 -print("\n=== AAA 종목 상세 ===") -aaa = conn.execute(""" - SELECT mr.ticker, c.name, mr.DD, mr.EDF, mr.E, mr.D, mr.sigma_V - FROM merton_results mr JOIN companies c ON mr.ticker = c.ticker - WHERE mr.dd_rating = 'AAA' - ORDER BY mr.DD DESC - LIMIT 15 -""").fetchall() -for r in aaa: - # 수동 EDF 계산 - manual_edf = norm.cdf(-r[2]) - print(f" {r[0]} {r[1][:15]:15s} | DD={r[2]:8.2f} | EDF={r[3]:.2e} | manual_N(-DD)={manual_edf:.2e} | E={r[4]:.2e} D={r[5]:.2e}") - -# 3) AA- EDF 역전 확인 (AA-가 A+보다 높은 문제) -print("\n=== AA- vs A+ 비교 ===") -for grade in ["AA-", "A+", "A", "A-"]: - data = conn.execute(f""" - SELECT AVG(DD), AVG(EDF), MIN(EDF), MAX(EDF), COUNT(*) - FROM merton_results WHERE dd_rating = '{grade}' - """).fetchone() - print(f" {grade:5s}: DD평균={data[0]:.2f}, EDF평균={data[1]:.6f}, EDF범위=[{data[2]:.6f}, {data[3]:.6f}], N={data[4]}") - -conn.close() +"""Verify Excel file""" +import openpyxl +wb = openpyxl.load_workbook("outputs/EDF_Report_20260312.xlsx") +for s in wb.sheetnames: + ws = wb[s] + print(f" {s}: {ws.max_row} rows x {ws.max_column} cols")