feat(report): Excel report generator + data revalidation (SPAC/REIT filter, DD cap, monotonicity)

This commit is contained in:
EDF Agent
2026-03-12 00:12:38 +09:00
parent 0c0b2129eb
commit 864decdc8e
4 changed files with 943 additions and 37 deletions

View File

@@ -1,42 +1,55 @@
"""시가총액 backfill: per-ticker get_market_cap_by_date 재시도"""
import sqlite3, time
from pykrx import stock
"""등급역전 분석 + AAA EDF 진단"""
import sqlite3
import numpy as np
from scipy.stats import norm
conn = sqlite3.connect("data/edf.db")
# 샘플 5개로 정확한 API 동작 확인
tickers = ["005930", "000660", "005380", "035720", "068270"]
# 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()
for tk in tickers:
print(f"\n=== {tk} ===")
# Method 1: get_market_cap_by_date (fromdate, todate, ticker)
try:
cap = stock.get_market_cap_by_date("20250301", "20250307", tk)
time.sleep(0.3)
print(f" cap_by_date: {len(cap)} rows")
if len(cap) > 0:
print(f" columns: {list(cap.columns)}")
print(cap.tail(2))
except Exception as e:
print(f" cap_by_date ERROR: {e}")
# Method 2: get_market_fundamental_by_date
try:
fund = stock.get_market_fundamental_by_date("20250301", "20250307", tk)
time.sleep(0.3)
print(f" fundamental: {len(fund)} rows, columns: {list(fund.columns)}")
except Exception as e:
print(f" fundamental ERROR: {e}")
rating_order = ["AAA","AA+","AA","AA-","A+","A","A-","BBB+","BBB","BBB-",
"BB+","BB","BB-","B+","B","B-","CCC+","CCC","CCC-"]
# Method 3: get_exhaustive_info
try:
cap = stock.get_market_cap_by_ticker("20250307")
time.sleep(0.3)
if tk in cap.index:
print(f" cap_by_ticker: 시총={cap.loc[tk, '시가총액']:,.0f}, 주식수={cap.loc[tk, '상장주식수']:,}")
else:
print(f" cap_by_ticker: {tk} not found, total rows={len(cap)}")
break # 한번만 호출 (전체 시장 데이터)
except Exception as e:
print(f" cap_by_ticker ERROR: {e}")
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()