S316 — 테마/종목 발굴을 위해 새로 작성한 모듈 정리¶
작성: 2026-05-25 (Mon) 23:16 범위: 2026-02-20 ~ 05-22 walk-forward 백테스트 e4 시스템에 S316 작업으로 추가된 모듈/함수
1. 모듈 추가/수정 한 눈에¶
| 파일 | 신규/수정 | 역할 |
|---|---|---|
scripts/backtest/e4_portfolio_walk.py |
수정 | e4 메인. 아래 함수 추가됨 |
scripts/backtest/e4_rank_sweep.py |
신규 | rank 1~5 단독/누적 알파 감쇠 측정기 |
data/backtest/e4_portfolio/_daily_theme_rs_byTheme.parquet |
신규 캐시 | 테마 단위 일별 RS 시계열 |
재사용한 기존 자산 (재구현 안 함):
- scripts/quant/earnings/e1_acceleration.py → ea_panel.parquet (S312v2 산출)
- scripts/quant/earnings/fnguide_loader.py → fnguide_fs.parquet (S311 백필 98종)
- discovery/state_classifier.py + compute_order_flow/wyckoff/vsa_signals/volume_profile (e4가 기존부터 호출)
2. e4_portfolio_walk.py 신규 함수 (S316)¶
2-1. 매출 lookup (point-in-time)¶
build_revenue_yoy_lookup() -> dict
lookup_revenue_yoy(code, t, lookup) -> float | None
data/dart_cache/fnguide_fs.parquet (S311 백필)
- 산식: 분기 매출 / 4분기전 매출 - 1
- 가용 시점: 분기말 + 45일 보수 lag
- 출력: 종목별 [(avail_from, yoy), ...] 시계열
- 커버리지: 83종 (universe 200 중)
2-2. 퀀트 lookup (point-in-time, S312v2 ea_panel 활용)¶
build_quant_lookup() -> dict
lookup_quant(code, t, lookup) -> dict | None
data/backtest/e1/ea_panel.parquet (E1 모듈 산출)
- 산식: 3 metric (revenue/op_income/net_income) × {g_curr (YoY 가속), ea_qoq (QoQ 가속)} 평균
- 가용 시점: 분기말 + 45일 lag
- 출력: {stock_code: [(avail, {'g_avg', 'ea_qoq_avg'}), ...]}
- 커버리지: 98종
2-3. 테마 leader 4기준 산식 (S314 일반화)¶
_stock_metrics_at(code, t, closes_daily, ohlcv_cache) -> dict
_score_theme_members_at(codes, t, ...) -> list[dict]
scripts/quant/themes/theme_leader_select.py (S314, 1시점 → t 인자화)
- 4 기준 각 0~25:
- cum_return : 60일 수익률 테마 내 percentile
- downside_resilience : 60일 down-day 평균 손실 (작을수록 좋음)
- turnover_concentration : 20일 평균 거래대금 percentile
- reaction_speed : (ret_5d/5 - ret_60d/60) percentile
2-4. 통합 점수 풀 선정 (S316 23:00 PM 가르침)¶
select_daily_candidates_integrated(
t, universe, theme_df, themes_map, closes_daily, ohlcv_cache,
chart_state_df, revenue_lookup, quant_lookup,
*, top_n=15, weights=None
) -> list[str]
2-5. 구 컷오프 선정 (보존, 비활성)¶
select_daily_candidates(..., use_integrated=True, ...)
use_integrated=False로 구 방식 (테마 rank slice × leader rank slice) 사용 가능.
- 구 방식 파라미터: theme_rank_start/end, leader_rank_start/end, require_revenue_positive, require_chart_positive
2-6. can_enter() — OF 게이트 (S316 21:13 PM 가르침)¶
can_enter(row) -> bool
2-7. compute_theme_rs_for_dates() — 출력 확장¶
compute_theme_rs_for_dates(dates) -> tuple[pd.DataFrame, pd.DataFrame]
_daily_theme_rs_byTheme.parquet (5,103행)
2-8. build_universe() — 시총 → 거래대금 (S316 22:48 PM 가르침)¶
build_universe() -> list[str]
3. 흐름 (S316 v5 — 현재 상태)¶
[Phase A] build_universe()
거래대금 상위 200종 → universe
[Phase B] compute_daily_state_for_universe()
종목 × 날짜 × 일봉모듈(wyckoff/vsa/laws/volprofile) + 30분봉 OF
→ chart_state_df (state/phase/chart_score/of_*)
캐시: _daily_chart_state.parquet (12,580 rows)
[Phase C] compute_theme_rs_for_dates()
ats_main themes × KOSPI 일봉 + universe 일봉
→ (stock_df, theme_df) 2개
캐시: _daily_theme_rs.parquet (15,532) + _daily_theme_rs_byTheme.parquet (5,103)
[Phase C-2] build_revenue_yoy_lookup() + build_quant_lookup()
fnguide_fs.parquet + ea_panel.parquet
→ 종목별 point-in-time 시계열
[Phase D] run_portfolio()
매일 종가:
1) 보유 종목 should_exit() 체크 → 청산
2) select_daily_candidates() = 통합 점수 top 15
3) can_enter() 통과 + compute_score >= 0.3 → 진입
4) NAV 기록
4. 현재 결함 (PM 지적 미해결)¶
| 결함 | 위치 | 영향 |
|---|---|---|
| 풀 선정 점수와 진입 점수 비일관 | compute_score() line 535 |
풀 1위(삼성)와 진입 1위(현대건설) 다름 |
| 1종 100% 몰빵 로직 | line 580-589 (1.5배 격차 시 1종) + line 609 (cash=0) | 첫 진입 후 cash 소진 → 추가 진입 0 |
| OF buy_intent 폐기 | can_enter() |
"매수 우위 약한" 종목도 통과 (현대건설 buy_pres < sell_pres) |
| 매출 데이터 부족 | fnguide_fs 98종, universe 200 중 83종 커버 | 매출 percentile에 NaN 다수 → 중간값 0.5 부여로 약화 |
5. 결과 변화 (NAV / KOSPI 대비)¶
| 버전 | 풀 선정 | 진입 점수 | NAV | α vs KOSPI |
|---|---|---|---|---|
| S315 | 시총100 정적 | 4축 AND OF | -10.76% | -45.87pp |
| v1 | 강한테마 top3 × leader top3 (컷오프) | 옛 공식 | +2.45% | -32.66pp |
| v2 | 동일 + OF 정정 | 옛 공식 | +58.10% | +22.99pp |
| v3 | 동일 + 매출/차트 추가 | 옛 공식 | +47.40% | +12.29pp |
| v4 | 동일 + universe 200 (거래대금) | 옛 공식 | +47.40% | +12.29pp |
| v5 (현재) | 통합 점수 top 15 | 옛 공식 (불일치) | +17.15% | -17.96pp |
v5가 v4보다 NAV 떨어진 이유 = 풀은 다양화됐는데 진입 점수가 옛 공식 그대로라서 풀 1위 못 사고 옛 공식 1위(현대건설) 1종에 몰빵.
6. 다음 단계 (PM 결정 대기)¶
- compute_score() 폐기 → 풀 통합 점수를 그대로 진입 score로
- 1종 100% 몰빵 로직 폐기 → 통합 점수 상위 N개 균등/score 비례 분산
- can_enter()에 buy_intent 복구
- 매출 백필 확장 (98종 → universe 200 전체)