이 스킬
Value 팩터 composite (Fama-French B/M + E/P + CF/P)
Fama-French 1992 의 Value 팩터를 B/M (Book/Market) + E/P (Earnings yield) + CF/P (Cash flow yield) 3 정의 평균으로 composite 산출. 단일 회사 + peer set percentile rank. 단일 지표 (PER 단독) 함정 회피. 트리거 — 'Value 팩터 composite (Fama-French B/M + E/P + CF/P)', 'value factor', 'valueFactor'.
연계 절차
이 절차의 단계
- 1 Quality 팩터 — QMJ (Frazzini-Pedersen-Asness 2014)
recipes.quant.qualityFactorvalue × quality 결합 (Asness QMJ junk 제거).
- 2 Damodaran Peer Multiple 분해
recipes.fundamental.valuation.damodaran.peerMultipleDecomposition단순 multiple 함정 검증.
- 3 산업 단계 매핑 — ROIC-WACC spread 분포로 phase 판정
recipes.industry.industryStagePhasephase 와 value rank 정합.
절차
실행 순서
- 1
회사 market cap + 재무 BS·IS·CF (Company.panel)
- 2
B/M (Book/Market) = 자본 / 시총
- 3
E/P (Earnings yield) = 순이익 / 시총
- 4
CF/P (Cash flow yield) = 영업CF / 시총
- 5
peer set 단면 분포
- 6
적자 회사 (E/P 음수) 의 E/P 분위 의미 X — earnings normalized 필요.
- 7
자본잠식 회사 B/M 음수 — peer 비교에서 outlier.
- 8
금융사 valuation 정의 다름 (P/PreProvisionPPnR 등) — 비교 무의미.
- 9
IFRS / GAAP 차이로 자본 raw 비교 노이즈.
- 10
composite ≥ 0.75: `recipes.quant.qualityFactor` 로 quality 동행 확인 (cheap + high quality = value 후보).
- 11
composite ≥ 0.75 + Quality z < 0: value trap 후보 — `recipes.fundamental.quality.forensics.deepDive` 회계 fact check.
- 12
composite < 0.25 + momentum 강함: growth 후보 — `recipes.fundamental.valuation.damodaran.growthFeasibility` 로 성장 정합 확인.
예시
이런 질문이 들어오면 이 skill 을 쓴다
- 005930 value 팩터 composite 어디
- Fama-French value 상위 종목 (B/M + E/P + CF/P)
- 가치주 cross-section rank — PER 단독 함정 회피
출력
기대 결과
- B/M · E/P · CF/P 각각 단일값
- composite z-score (3 정의 평균) + percentile rank
- peer 상위 quartile 종목 list
공개 호출 방식
import dartlab
import polars as pl
target = "005930"
c = dartlab.Company(target)
# 밸류 지표는 전종목 스캔에서 한 번에 받는다. PER·PBR 의 역수가 각각 earnings yield · book-to-market.
valuation = dartlab.scan("valuation") # 종목코드 · 종목명 · 시가총액 · PER · PBR · PSR · 배당수익률
def value_metrics(code):
row = valuation.filter(pl.col("종목코드") == code)
if row.height == 0:
return None
per = float(row["PER"][0] or 0)
pbr = float(row["PBR"][0] or 0)
psr = float(row["PSR"][0] or 0)
return {
"bm": 1 / pbr if pbr > 0 else 0, # book-to-market
"ep": 1 / per if per > 0 else 0, # earnings yield
"sp": 1 / psr if psr > 0 else 0, # sales yield
}
own = value_metrics(target)
try:
peers = c.industry()["peers"][:20]
except Exception:
peers = []
peer_rows = []
for p in peers:
code = p.get("code") or p.get("stockCode")
if not code or code == target:
continue
m = value_metrics(code)
if m:
peer_rows.append({"code": code, **m})
# percentile rank — 각 정의별로
def rank(metric, my_val):
vals = sorted([r[metric] for r in peer_rows if r[metric] is not None])
if not vals or my_val is None:
return None
below = sum(1 for v in vals if v < my_val)
return below / len(vals)
if own:
rank_bm = rank("bm", own["bm"])
rank_ep = rank("ep", own["ep"])
rank_sp = rank("sp", own["sp"])
ranks = [r for r in (rank_bm, rank_ep, rank_sp) if r is not None]
composite = sum(ranks) / len(ranks) if ranks else None
else:
rank_bm = rank_ep = rank_sp = composite = None
table = pl.DataFrame([{
"metric": "valueComposite",
"ownBm": own["bm"] if own else None,
"ownEp": own["ep"] if own else None,
"ownSp": own["sp"] if own else None,
"rankBm": rank_bm,
"rankEp": rank_ep,
"rankSp": rank_sp,
"composite": composite,
"peerCount": len(peer_rows),
}])
emit_result(
table=table,
values={"composite": composite, "peerCount": len(peer_rows)},
date=None,
sources=["dartlab://scan/valuation", "dartlab://industry/peers"],
) 호출 동작
1. 결론 도출
value composite rank 단정 (0~1, 1 = 가장 cheap). 예: “B/M peer rank 0.82, E/P 0.78, CF/P 0.65 → composite 0.75 (top quartile) → value 후보 강함.”
2. 핵심 근거 수집
- 회사 market cap + 재무 BS·IS·CF (Company.panel)
- B/M (Book/Market) = 자본 / 시총
- E/P (Earnings yield) = 순이익 / 시총
- CF/P (Cash flow yield) = 영업CF / 시총
- peer set 단면 분포
3. 메커니즘 분석
재무제표 → 3 yield 정의
B/M = totalEquity / marketCap
E/P = netIncome / marketCap
CF/P = operatingCF / marketCap
↓
peer 단면 분포에서 percentile rank (오름차순)
rank_BM = (own_BM > peer_BM 카운트) / peer_count (1=가장 cheap)
rank_EP = (own_EP > peer_EP 카운트) / peer_count
rank_CFP = (own_CFP > peer_CFP 카운트) / peer_count
↓
composite = (rank_BM + rank_EP + rank_CFP) / 3
composite ≥ 0.75 → cheap value 후보 (top quartile)
0.25-0.75 → fair value
composite < 0.25 → expensive (bottom quartile) 3 정의 동시 cheap = 강한 value 신호 (단일 지표 PER 함정 회피). E/P 만 높고 B/M·CF/P 낮으면 일회성 이익 (value trap) 위험.
4. 반례·한계
- 적자 회사 (E/P 음수) 의 E/P 분위 의미 X — earnings normalized 필요.
- 자본잠식 회사 B/M 음수 — peer 비교에서 outlier.
- 금융사 valuation 정의 다름 (P/PreProvisionPPnR 등) — 비교 무의미.
- IFRS / GAAP 차이로 자본 raw 비교 노이즈.
5. 후속 모니터링
- composite ≥ 0.75:
recipes.quant.qualityFactor로 quality 동행 확인 (cheap + high quality = value 후보). - composite ≥ 0.75 + Quality z < 0: value trap 후보 —
recipes.fundamental.quality.forensics.deepDive회계 fact check. - composite < 0.25 + momentum 강함: growth 후보 —
recipes.fundamental.valuation.damodaran.growthFeasibility로 성장 정합 확인.
대표 반환 형태
| column | 의미 |
|---|---|
ownBm/Ep/Cfp | 본 회사 3 정의 값 |
rankBm/Ep/Cfp | peer 단면 percentile (0~1) |
composite | 3 rank 평균 |
peerCount | peer 표본 |
연계 절차
- recipes.quant.qualityFactor - value × quality 결합 (Asness QMJ junk 제거).
- recipes.fundamental.valuation.damodaran.peerMultipleDecomposition - 단순 multiple 함정 검증.
- recipes.industry.industryStagePhase - phase 와 value rank 정합.
기본 검증
- peer < 10 이면 percentile rank 불안정 — 한계 명시.
- E/P < 0 (적자) 회사는 EP 정의 제외, 나머지 2 로만 composite.
- 단일 정의 (PER 단독) 결론 금지 — 3 정의 분리 표기.
런타임
실행 환경별 호환성
| 환경 | 상태 | 비고 / 제한 |
|---|---|---|
| Local Python | supported | · |
| Server | supported | · |
| MCP | supported | · |
| Web AI | limited | · |
| Pyodide | limited | · |