이어 가기
- Company
engines.company - Gather
engines.gather - Scan - 비율
engines.scan.ratio - Quality + Value 횡단 스크리닝 (Novy-Marx GP/A 기반)
engines.recipe.qualityValueScreen - Compounder 후보 — ROE/매출/마진 5 년 일관성 스크리닝
engines.recipe.compounderCandidates - 자본배분 점수카드 (FCF 5 사용처 + ROIIC + SGR — L1 raw)
engines.recipe.capitalAllocationScorecard
절차
실행 순서
- 1
**TaxBurden** — 세후 / 세전. 1 에 가까울수록 세부담 낮음. 산업·국가·세제 의존.
- 2
**InterestBurden** — 세전 / 영업이익. 1 에 가까울수록 이자비용 낮음. 부채 자본 구조 반영.
- 3
**OperatingMargin** — 영업이익 / 매출. 사업 본업의 수익성.
- 4
**AssetTurnover** — 매출 / 평균자산. 자산 효율 (자본 회전).
- 5
**FinancialLeverage** — 평균자산 / 평균자기자본. 부채를 통한 자본 증폭 — equity multiplier.
- 6
Penman (2013): DuPont 분해가 future earnings 예측에서 단순 ROE 보다 우월. RNOA (Return on Net Operating Assets) 와 결합 시 가장 강한 신호.
- 7
Soliman (2008): 산업 평균 대비 OpMargin·Turnover 분해로 미래 수익률 예측 가능.
- 8
`year : str` — 4 기간 (5 년 시계열에서 평균자산 계산으로 1 기간 손실)
- 9
`taxBurden : float` — NI/EBT
- 10
`interestBurden : float` — EBT/EBIT
- 11
`operatingMargin : float` — EBIT/Sales
- 12
`assetTurnover : float` — Sales/AvgAssets
학술 근거
DuPont 사 (E.I. du Pont de Nemours, 1920 년대) 가 도입한 ROE 분해. CFA Institute Level 1 표준 (CFA Curriculum, FRA Reading 22). 핵심 식:
ROE = (NI / EBT) × (EBT / EBIT) × (EBIT / Sales) × (Sales / Avg Assets) × (Avg Assets / Avg Equity) = TaxBurden × InterestBurden × OperatingMargin × AssetTurnover × FinancialLeverage
각 항목의 의미:
- TaxBurden — 세후 / 세전. 1 에 가까울수록 세부담 낮음. 산업·국가·세제 의존.
- InterestBurden — 세전 / 영업이익. 1 에 가까울수록 이자비용 낮음. 부채 자본 구조 반영.
- OperatingMargin — 영업이익 / 매출. 사업 본업의 수익성.
- AssetTurnover — 매출 / 평균자산. 자산 효율 (자본 회전).
- FinancialLeverage — 평균자산 / 평균자기자본. 부채를 통한 자본 증폭 — equity multiplier.
ROE 가 같은 두 회사도 5 동인 분포가 다르면 사업 모델이 완전 다름. 예: 명품 회사는 OpMargin 높고 Turnover 낮음, 마트는 반대.
검증 사례:
- Penman (2013): DuPont 분해가 future earnings 예측에서 단순 ROE 보다 우월. RNOA (Return on Net Operating Assets) 와 결합 시 가장 강한 신호.
- Soliman (2008): 산업 평균 대비 OpMargin·Turnover 분해로 미래 수익률 예측 가능.
L1 데이터로 직접 계산 (analysis axis 미사용)
dartlab 의 c.show() 가 L1 provider 데이터 그대로 노출. analysis 엔진의 profitability / efficiency axis 결과를 의존하지 않고 raw 시계열에서 직접 분해.
공개 호출 방식
import dartlab
import polars as pl
c = dartlab.Company("005930")
is_df = c.show("IS", freq="Y") # 손익계산서 (매출·영업이익·세전·순이익)
bs_df = c.show("BS", freq="Y") # 재무상태표 (자산·자본 시계열)
years = ["2025", "2024", "2023", "2022", "2021"]
def dupontDecompose(is_df: pl.DataFrame, bs_df: pl.DataFrame, years: list[str]) -> pl.DataFrame:
sales = is_df.filter(pl.col("snakeId") == "sales").select(years).to_numpy()[0]
op = is_df.filter(pl.col("snakeId") == "operating_profit").select(years).to_numpy()[0]
ebt = is_df.filter(pl.col("snakeId") == "earnings_before_tax").select(years).to_numpy()[0]
ni = is_df.filter(pl.col("snakeId") == "net_income").select(years).to_numpy()[0]
assets = bs_df.filter(pl.col("snakeId") == "total_assets").select(years).to_numpy()[0]
equity = bs_df.filter(pl.col("snakeId") == "total_stockholders_equity").select(years).to_numpy()[0]
avg_assets = [(assets[i] + assets[i+1]) / 2 for i in range(len(years)-1)]
avg_equity = [(equity[i] + equity[i+1]) / 2 for i in range(len(years)-1)]
rows = []
for i, y in enumerate(years[:-1]):
rows.append({
"year": y,
"taxBurden": ni[i] / ebt[i],
"interestBurden": ebt[i] / op[i],
"operatingMargin": op[i] / sales[i],
"assetTurnover": sales[i] / avg_assets[i],
"financialLeverage": avg_assets[i] / avg_equity[i],
"roeReconstructed": (ni[i] / ebt[i]) * (ebt[i] / op[i]) * (op[i] / sales[i])
* (sales[i] / avg_assets[i]) * (avg_assets[i] / avg_equity[i]),
})
return pl.DataFrame(rows)
result = dupontDecompose(is_df, bs_df, years) 호출 동작
c.show("IS", freq="Y")— 5 기간 손익계산서 시계열 (snakeId 기반 wide).c.show("BS", freq="Y")— 5 기간 재무상태표.- snakeId 로 6 항목 추출 — sales / operating_profit / earnings_before_tax / net_income / total_assets / total_stockholders_equity.
- 평균자산 / 평균자본 계산 (직전년도 + 당년도 / 2).
- 5 동인 직접 계산 + 재구성 ROE (5 곱) — 원본 ROE 와 일치 검증.
대표 반환 형태
result : pl.DataFrame — 컬럼:
year : str— 4 기간 (5 년 시계열에서 평균자산 계산으로 1 기간 손실)taxBurden : float— NI/EBTinterestBurden : float— EBT/EBIToperatingMargin : float— EBIT/SalesassetTurnover : float— Sales/AvgAssetsfinancialLeverage : float— AvgAssets/AvgEquityroeReconstructed : float— 5 동인 곱 (원본 ROE 일치 확인)
한계
earnings_before_taxsnakeId 가용성 — 일부 회사 IS 에서 영업외손익 분리 안 됨. fallback 으로net_income / (1 - taxRate)추정.- 평균자산 단일 시점 가중 — Q1·Q2·Q3·Q4 가중평균이 더 정확하나 본 recipe 는 연말 + 전년말 평균만.
- 연결 vs 별도 — 한국 회사 연결재무 기준. 자회사 효과 큰 지주회사는 분해 해석 주의.
- 금융업 부적합 — 은행·보험 IS 구조 다름 (이자수익·보험료 매출). 별도 분해 framework 필요.
한국 / 미국 시장 차이
- 한국: 법인세율 변동 작아 TaxBurden 안정. 영업외손익 비중 큰 회사 (지주·금융 자회사) 다수 → InterestBurden 변동 크게 나타나는 경우 주의.
- 미국: 본 framework 의 본 시장. S&P 500 대표 회사 5 동인 평균 배포 — Damodaran 산업 데이터로 비교 가능.
연계 절차
- 본 recipe → ROE 5 동인 시계열 도출.
- 동인 변화 큰 (표준편차 큰) 항목 식별 — ROE 변동의 원인.
- OperatingMargin 변동 →
engines.recipe.workingCapitalQuality로 운전자본 효율 점검. - AssetTurnover 변동 → 자산 재투자 시점 (CAPEX 시계열) 분석.
- FinancialLeverage 변동 →
engines.recipe.creditDistressDual로 부채 위험 점검. - 5 년 일관 quality compounder 인지 →
engines.recipe.compounderCandidates와 상호 검증.
기본 검증
roeReconstructed와 원본 ROE (net_income / avg_equity) 차이 ≤ 0.5%p 이어야 분해 정확.- 5 동인 변화율 표 확인 — 한 동인이 전체 ROE 변화의 70% 이상 설명하면 단일 동인 사이클.
- 산업 평균 분해와 비교 — 본 회사가 동종 대비 어느 동인이 우월/열위인지.
- 음수 영업이익 시 InterestBurden 무한대 — 흑자 5 년 회사만 적용 권장.
- “ROE 15% = 좋다” 단정 X — 5 동인 분포로 구조적 우위 (margin·turnover) vs 일시적 (leverage 상승) 구분.
런타임
실행 환경별 호환성
| 환경 | 상태 | 비고 / 제한 |
|---|---|---|
| Local Python | supported | — |
| Server | supported | — |
| MCP | unknown | — |
| Web AI | unknown | — |
| Pyodide | limited |
|