콘텐츠로 이동

score_compose 재설계 — 다축 복구 + 재료(catalyst) 축 신설

작성: 2026-05-29 (S329) 배경: 010170 대한광통신이 D7 1위로 선정됐으나 -15.76% 급락. 원인 추적 결과 score 4축 중 RS 1축만 작동 + 재료 축 부재. Status: 📋 설계 — 구현 전 PM 승인 대기


0. 실측 진단 (20260527 run, 80후보)

4축 작동률

score축 비-0 비율 연결 도구 원인
stock_rs_z 80/80 (100%) D3 ✅ 유일 작동
accel_z 0/80 D2 차트 OHLCV 컬럼 대소문자 (데이터 충분, 코드가 못 읽음)
ofm_cvd_z_z 0/80 D5 수급/OF 30분봉 컬럼 대소문자 (데이터 충분, 코드가 못 읽음)
rs_vs_theme_z 0/80 D4 테마 코드 미완성 (placeholder)
(재료) 축 자체 없음 discover에 재료 입력 경로 부재

final_score ≈ stock_rs_z 단일. 010170은 1년 +3517% 폭등 → RS z=4.735 극단 → 1위. 반도체 대장 SK하이닉스 RS z=0.728 → 12위.

차트·수급 결손 근본 원인 = 컬럼 대소문자 (실측 확정 2026-05-29)

  • data/backtest/s303/ohlcv/010170.parquet = 1197행 (2021~2026), 컬럼 ['Open','High','Low','Close','Volume','Change'] 대문자
  • chart_indicators.py:29 if "close" not in ohlcv.columns → 소문자 못 찾아 trend_insufficient 외 6블록 전부 결손
  • data/minute_charts/30min/010170.parquet = 900행, 컬럼 ['Open','High','Low','Close','Volume'] 대문자
  • of_indicators.py:12 {"close","volume"}.issubset(bars.columns) → 소문자 못 찾아 cvd/bvc/pressure/absorption 전부 결손
  • 데이터 부족이 아니라 컬럼명 한 글자. position_status.py만 S329에서 _col()로 고쳤고 chart_indicators / of_indicators / stock_rs / theme_rs 미수정 (followup A).
  • 결론: score_compose 수정만으로 안 됨. 컬럼 정규화(A)가 선행돼야 accel_z·ofm_cvd_z가 실값을 가짐. "모집단 제외" 우회는 임시방편일 뿐, 차트/수급을 실제로 보려면 결손 자체를 제거해야 한다.

재료 진단 (장마감 리포트는 이미 경고했음)

docs/daily_reports/20260526_post_market.md UNIT5/6이 010170을 정확히 판정: - 재료강도 soft (테마 편승), verdict WATCH - "당일 직접 catalyst 검색 미발견, 5/11 +25%·5/13 -15% 고변동성", "1년 +3517% 누적 후 모멘텀 추격 구분 필요"

반면 같은 표에서 삼성전기(MLCC)·LG이노텍·DB하이텍 = hard·TRACK. → discover가 이 리포트를 입력으로 안 받아 soft·WATCH 경고를 무시하고 RS만으로 선정.


1. score_compose 버그 수정 (4개)

대상: scripts/discover/indicators/score_compose.py

버그1 — rs_vs_theme 미완성 (line 14/33/39)

현행: rs_vs_theme = 0 하드코딩 + _z([0], 0) → 무조건 0. 수정: rs_vs_theme = stock_rs - theme_rs 실제 계산 → 전체 후보 모집단 z. (종목이 자기 테마보다 강한가 = 테마 내 주도력)

버그2 — 펀더멘탈 곱셈 소멸 (line 45)

현행: final_score = raw_score * ea_w → RS 단일 시 stock_rs_z × 0.5~1.0, D6 NEGATIVE도 양수 가중이라 순위 못 바꿈. 수정: 펀더멘탈을 합산 축으로. ea_contribution = z(ea_z) 추가하거나, D6 verdict 기반 가감점(POS +α / NEG −α). 곱셈 가중 폐기.

버그3 — 결손 0치환 → 모집단 오염 (line 16/18/34/35)

현행: accel = d2.get("accel_ratio") or 0 — UNAVAILABLE이 0으로 모집단에 섞여 평균/표준편차 왜곡. 수정: 결손은 Nonez 모집단에서 제외. 결손 종목의 해당 축 기여=0(중립)이되, 평균은 가용값만으로 계산. (verdict UNAVAILABLE/UNKNOWN = 제외)

버그4 — verdict 다수결 게이트 부재

현행: RS 단독 극단으로 통과 가능. 수정: score 산출 후 게이트 — 예: POSITIVE verdict 2축 미만 AND 단일축 z>3 = 과열 의심 플래그 + score 감점 또는 D7 selector에 경고 전달. (v6 백테스트 multi-axis 동시충족 가정 복원)


2. 재료(catalyst) 축 신설 (5번째 축)

데이터 소스 (살아있는 리포트)

파일 사용 UNIT 추출
docs/daily_reports/{date}_post_market.md UNIT5 상한가/급등 WHY 종목별 |재료강도|테마|verdict| 표
UNIT6 신규 발견 종목별 |유형|태그|WHY|테마| 표
UNIT4 테마 강도 랭킹 테마별 등급(LEADING/STRONG/COOLING)
docs/evening_reports/{date}_evening.md 글로벌→한국 테마 파급 매크로 연결 재료

폐기: data/news_curator/* (구 리포팅 시스템, 미사용 — PM 확인).

파싱 규칙 (마크다운 표)

UNIT5 표 헤더: | 종목 | 등락% | WHY | 재료 | 테마 | verdict | - 종목명에서 (코드) 정규식 추출 → 후보 코드 매칭 - 재료강도: hard / soft / none (괄호 주석 무시, 첫 단어) - verdict: TRACK / WATCH / NOISE - UNIT6 태그: [NEW] / [TRACKED] / [추정] / [검증]

재료 점수화 (catalyst_z)

신호 점수
재료강도 hard +1.0
재료강도 soft +0.3
재료강도 none -0.5
verdict TRACK +0.5
verdict WATCH 0
verdict NOISE -1.0
"catalyst 미발견" + 급등누적(1년 +N%) 과열 페널티 -1.0
리포트 미등장 (UNIT5/6에 없음) 0 (중립, 결손 아님 — 급등 안 한 종목)

→ 후보 80개의 catalyst raw score → 모집단 z = catalyst_z. → 검증 효과: 010170 = soft(+0.3) + WATCH(0) + 과열페널티(-1.0) = -0.7 → z 음수 → 감점. 삼성전기 = hard(+1.0)+TRACK(+0.5) = +1.5 → z 양수 → 가점.

최종 산식 (5축)

final_score = z(rs_vs_theme) + z(stock_rs) + z(accel) + z(ofm_cvd) + z(catalyst) + ea_contribution
              (모든 z는 결손 제외 모집단)
+ 게이트: POSITIVE 2축 미만 → 과열 플래그

3. 구현 범위 (PM 승인 후)

Step 파일 변경
0 (선행·필수) chart_indicators.py / of_indicators.py / stock_rs.py / theme_rs.py 컬럼 대소문자 정규화_col() 헬퍼(position_status.py 기존 패턴) 일괄 적용 또는 빌더 진입 시 df.columns = [c.lower() for c in df.columns]. → accel_z·ofm_cvd_z 실값 복구. 이게 안 되면 차트/수급은 영원히 0.
1 scripts/discover/indicators/catalyst_parser.py (신규) post_market.md UNIT5/6 표 파싱 → 종목별 {강도,verdict,과열플래그}
2 scripts/discover/indicators/score_compose.py 버그1-4 수정 + catalyst_z 5번째 축 + 게이트
3 scripts/discover/builders/build_d7_input.py catalyst 데이터를 후보 raw_metrics에 포함
4 .claude/agents/discover/portfolio-selector.md 과열 플래그·재료 verdict 해석 규칙 추가
5 검증 20260527 run 재실행 → 010170 순위 하락 + 삼성전기/LG이노텍 상승 + accel_z/ofm_cvd_z 비-0 확인

Step 0이 살아나면 실제로 보게 되는 것 (010170 차트·수급)

  • 차트(accel_z, D2): 1년 +3517% 급등 → ma slope_z 극단·고변동성·52w high 근접. accel_ratio(최근 가속도)가 음수면 "급등 후 꺾임" → 과열 차트 신호. 지금은 컬럼 결손으로 이 신호 전체가 0.
  • 수급(ofm_cvd_z, D5): 30분봉 CVD(매수-매도 누적). 외인·기관 매도 우위면 cvd_z 음수 → "가격은 올랐는데 매도 우위" 다이버전스. 010170은 5/12 외인 277만주 순매도였으나 D5 결손으로 못 봄.
  • → 차트·수급이 살아나면 010170은 RS만 양수, accel/ofm은 음수(과열·매도) → 다축 합산 시 RS 단독 우위 해소.

의존: Step 0(컬럼 정규화)이 전제. Step 0 없이 score_compose만 고치면 버그1·2·4·재료축만 효과(여전히 차트·수급은 못 봄). PM 지적대로 차트·수급을 "실제로 보려면" Step 0이 필수.


4. 기대 효과 (010170 케이스 역산)

현행: 010170 final=3.788(RS단독 1위) → 선정 → -15.76%. 수정 후 예상: - accel/ofm 결손 제외로 RS 가중 상대 하락 - catalyst_z = soft+WATCH+과열 = 음수 → 추가 감점 - 게이트: D2 UNAVAIL·D3 NEUTRAL·D5 UNKNOWN → POSITIVE 1축(D4)뿐 → 과열 플래그 → 010170 순위 급락, hard·TRACK 반도체주(삼성전기·LG이노텍·DB하이텍) 상위 복귀.