Topic — /discover 파이프라인 보완 (S329 첫 운영일 발견)¶
Last Updated: 2026-06-15 (Mon 00:53) Status: 🔄 진행 중 선행: S327(원자화) + S328(orchestrator) + 2026-05-27 첫 실행 (T0=20260527)
★★★★★ 갱신 (S353 2026-06-21) — D5·D4 profile도 selector 노출 (PM: "데이터 제대로 활용·해석하나?" → 7인자 전수 점검):
f6 해결 후 PM이 "그럼 전체 데이터를 제대로 쓰나?" 질의 → 6/18 selected(삼성전자) 7인자 전수 점검. f6와 같은 갭 2곳 발견: D5(오더플로)·D4(테마)가 질적 데이터(cvd_price_divergence·absorption / lifecycle_stage·rs_vs_theme)를 만들고도 _DOM_PROFILE에 없어 selector에 raw 숫자만 가던 것. 수정: _DOM_PROFILE에 D5(cvd_price_divergence·absorption_score_last·ofm_bvc_5bar)·D4(rs_vs_theme·theme_rank) 추가 → domain_reasoning + must_address. must_address 루프에 D4 추가(D6/D2/D5/Dnews만→+D4). ★거짓통과 방지: 작은 절댓값 숫자(|x|<1, divergence 0·bvc 0.6 등)는 "0"이 출력 아무데나 매칭되니 must_address 강제 제외(profile 노출은 유지) — 강제는 라벨(unjustified/fresh)+변별력 큰 수치(forward_per·theme_rank)에만. 검증: D5·D4 profile selector 노출 ✅, D4_theme_rank=2 강제 포함·D5 작은수치 제외 ✅.
- ★ lifecycle_stage 제외 (PM 정정 2026-06-21 "만든 적 없다"): 처음 D4 profile에 lifecycle_stage 넣었으나, 추적 결과 = compute_lifecycle이 dashboard.json 테마 lifecycle(emerging/leading 등)을 incubation/peak로 이름만 바꿔 끌어온 placeholder성 필드 + 실측 전 종목 None(미작동) + PM이 2026-06-02에 "lifecycle=가격축이라 verdict서 제외" 결정한 흔적(step_loop_d2to6:230). PM 핵심 인자 아님 → D4 profile에서 제거. 내 오판(설계 안 한 필드를 selector 노출 대상에 넣음) 정정.
- 답(PM 질문 "데이터 제대로 활용하나"): f6·f3·D5·D4(rs_vs_theme·theme_rank) profile 노출로 5개 도메인 실제 인자가 selector 도달. lifecycle은 placeholder라 제외(데이터 생산 자체가 안 됨, PM 미설계).
★★★★ (이전) 갱신 (S353 2026-06-21) — f6 선반영의 근원 한계 진단 + FnGuide forward 컨센서스 복구 (PM: "많이 올랐다가 아니라 향후 매출·컨센서스로 정당화 가능성을 봐야"):
PM이 must_address 강제 뒤에 더 근본을 짚음 — "f6 선반영도가 단순 '많이 올랐다'가 아니라 향후 매출·시장상황·컨센서스로 현재가 정당화될 가능성을 봐야". 코드 검증 결과:
- f6 현황: valuation_implied.py assess_price_justification이 3축(상승분vs실적·절대멀티플·implied 필요성장)으로 정당화 가능성은 봄(단순 상승률 아님). 단 forward 컨센서스 없음(docstring "컨센서스 없이 TTM으로"), industry_growth 하드코딩 10%.
- 데이터 가용성 조사(PM 지시): 키움 ka10001 raw 47필드 전수 → forward 0건(per/eps/roe 전부 과거 TTM). DART도 과거. → FnGuide 실호출: HTML에 (E) 32회·2026/12·2027/12 컨센 실재하나 fnguide_loader.parse_main이 forward 0건 파싱(S312 "is_estimate 모두 False" 결함 잔존).
- ★ 파서 forward 복구 (코드 2곳): ①_parse_label re.match→re.search — forward 컬럼 라벨이 '(E) : Estimate 컨센서스, 추정치 2026/12(E)' 형태라 연도가 시작 위치에 없어 match 실패하던 것. ②_match_metric 신설(정확매칭→접두매칭) — 'EPS(원) 지배주주순이익 / 수정평균주식수 EPS (원)'처럼 계산식 붙은 라벨이라 ==가 실패하던 EPS·PER·ROE 누락 복구. 긴 키 우선(영업이익률>영업이익).
- 검증 ✅: 005930 forward 복구 — 2026/12 EPS 44,197·forward PER 8.01배, 2027/12 EPS 58,374·PER 6.06배, 매출 696.7조→857.9조. 3종목 회귀(000660·005380) 실측 보존+컨센 54건. 6/18 결함 직결: f6가 과거 PER 53.93으로 unjustified 판정했으나 forward PER 6~8배면 정당화 가능 = selector가 "ea_z 가속으로 정당화"라고 텍스트로만 추정한 걸 실데이터로 검증 가능해짐.
- ✅ forward를 f6에 연결 (S353): valuation_implied.py에 fetch_forward_consensus(code) 신설(fnguide forward 연간 컨센→forward PER·EPS/매출 CAGR). assess_price_justification에 축4(forward 정당화 가능성) 추가 — forward PER이 (a)업종성장 수준 이하거나 (b)과거 PER 대비 반토막+이면 forward_justifies=True → 강플래그 1개 상쇄(unjustified→stretched 완화). 단 역성장(earnings_declining)·적자극단은 hard flag로 완화 차단. fetch_valuation이 forward까지 통째 반환. 검증 ✅: 6/18 삼성전자 과거 PER 46.8(unjustified)→forward PER 8.01배(EPS CAGR +14%)→stretched로 완화(PM "비싸 보이나 컨센이 받침"). 안전성 3케이스 통과(forward 없음→기존 유지/역성장→완화 차단/forward도 PER40 비쌈→완화 없음). end-to-end 하네스 실호출 OK.
- ✅ forward 수치를 selector까지 노출 (S353, PM 원칙 "활용 가능한 데이터를 꼼꼼히 보고 결론"): verdict 완화(stretched)만으론 selector가 "왜 완화됐나" 근거를 못 봄 → forward 수치 자체를 사슬 끝까지. 3파일: ①fund_obs_synth._fetch_valuation_block이 forward_per_next·eps_cagr·rev_cagr·forward_justifies 추출 ②D6 envelope에 forward 필드 추가(synth_in.valuation에서 직접, LLM 추출 아님=수치 보존) ③build_d7_input._DOM_PROFILE["D6"]에 forward_per_next·forward_eps_cagr_pct 추가 → domain_reasoning.D6.profile + must_address 자동 강제. 숫자 profile은 _num_hints로 소수 표기 매칭("8.0배"도 잡힘). 검증 ✅: 검증1(_fetch_valuation_block 실호출 005930→forward_per_next 8.01·eps_cagr 14.0·justifies True), 검증2(build→must_address에 D6_forward_per_next=8.01·D6_forward_eps_cagr_pct=14.0 강제 포함). 사슬 완성: fetch_valuation→block→D6 envelope→profile→must_address→selector 반드시 인용. 3파일 import 정합.
- ✅ A-1 industry_growth → 그 종목 forward EPS CAGR (S353): assess_price_justification 축3 분모를 업종 고정 10%→그 종목 forward EPS CAGR(컨센이 실제 본 성장)로. "현재가가 요구하는 성장 ÷ 컨센이 보는 성장" — req>1=가격이 컨센 이상 요구(과대). forward 없으면 10% 폴백(회귀안전, denom_basis 필드로 명시). 검증: 005930 필요성장 47.9% ÷ 컨센 14% = 3.42(이전 4.79=업종10%). forward PER 8배는 "받침"(완화)이나 axis3는 "컨센으로도 3.4배 요구=여전히 비쌈" → selector가 두 측면 다 보고 판단(PM "꼼꼼히").
- ✅ A-2 fnguide forward 캐시 연결 (S353): fetch_forward_consensus(use_cache=True) — _forward_from_cache(30일 신선도) 우선 조회, 없으면 fetch+append_fnguide 저장. _forward_from_rows로 fetch/캐시 추출 공용화. 검증: 1차 fetch 2.58s→2차 cache 0.01s(258배), 값 일치. 캐시 parquet에 forward 40행 저장(이전 is_estimate 0건=stale 갱신). discover 수십종 재호출 부하 해소.
- ✅ 과거 데이터 검증 (S353) — 6/15주 selected에 forward f6 적용: forward 유무로 verdict 변화 측정 — SK하이닉스 unjustified→stretched(forward PER 9.09·EPS CAGR 18%), 삼성전자 unjustified→stretched(PER 8.01·14%), 인텍플러스 stretched 유지(forward PER 90.49=컨센도 못 받침, 완화 안 됨). 핵심: 이전 시스템은 반도체 대장주를 전부 "거품(unjustified)"으로 깎아 메모리 슈퍼사이클 한복판 대장주 회피 → forward 연결로 "거품 아닌 받침 있는 고평가(stretched)"로 정확히 구분, 진짜 비싼 종목(인텍플러스)은 그대로 걸러냄. 단 forward는 밸류 측면만 — SK하이닉스(이김 +16)와 삼성전자(짐 -2.34)가 똑같이 stretched·justifies=True(승패는 D5·당일분배·수급 다축 몫, forward가 구분 못 하는 건 정상). ⚠️ look-ahead 한계: 현재(6/21) 컨센으로 과거 재평가(분기 단위 갱신이라 영향 제한적, 추정). PM 원칙 실증("시스템 판단이 미래에 안 맞을 수 있다=괜찮다. 대신 활용 데이터는 꼼꼼히 봤다").
- ✅ 수집 방식 = 후보 그때그때 (PM 2026-06-21 정정): "98종 일괄 갱신"은 틀린 방향(쓰지도 않을 종목 미리 긁는 낭비). discover는 게이트 통과해 D6 오는 후보 몇십 종만 forward 필요 → _fetch_valuation_block→fetch_forward_consensus가 그 시점에 수집(캐시 없으면 fetch+저장). 검증: 캐시 없는 005380(현대차) 호출→via=fetch(그때 수집)→재호출 via=cache. 일괄 배치 불필요. 기존 stale 98종은 그 종목이 후보 오를 때 자연 갱신.
- ✅ 실제 D6 재생성 통합 검증 (S353 — PM "시스템 보완 완료된거야?" 질의): 모의 D6가 아니라 실 LLM D6 재생성으로 end-to-end 확인. ★통합 버그 발견·수정: _call_synthesizer가 synth_in(dict) 아닌 synth_in_path(경로)만 받는데 forward 추출이 synth_in.get() 참조 → NameError. 실 재생성으로만 드러남(모의는 run_fund_obs_synth 직접 호출이라 회피됨). 수정: synth_in_path에서 valuation 재로드. 검증: 005930 실 D6 재생성→envelope forward_per_next=8.01·eps_cagr=14.0·valuation_verdict=stretched(완화 반영)→build→domain_reasoning.D6.profile.forward_per_next 8.01 도달→must_address D6_forward_per_next 강제. 사슬 전구간 실데이터 작동 확인. baseline 6/18 무손상(임시 run_dir).
- 답(PM "완료된거야?"): forward 사슬은 실 D6 재생성으로 통합 검증 완료(버그 1건 잡고 수정). 단 ⚠️전체 파이프(D1~D7 풀 e2e)는 미실행 — 이번은 D6 1종목+build만. score_compose 등 다른 stage는 이번 세션 미변경이라 영향 없으나, 평일 라이브 풀런 1회로 최종 자연 확인 권장.
★★★ (이전) 갱신 (S353 2026-06-21) — must_address 강제 메커니즘 구현 (PM: "의무사항 문장 추가가 아니라 코드+데이터 입력으로 강제"):
PM 지적의 근원("쓰기로 한 데이터를 selector가 실제 사용·해석하도록 강제")을 /rw verify_token 패턴으로 discover에 이식. 명세 문장이 아니라 코드+데이터 구조:
- (1) 데이터 schemas.py D7CandidateRow에 must_address 필드 신설. build_d7_input.py _build_must_address(): 게이트통과(D2 POSITIVE=selected 후보) 종목에 우리가 selector에 넘긴 핵심 데이터 각각(f6 valuation_verdict·f3 theme_rs·도메인 profile 라벨)을 {label, value, found_hint:[수치/한글의미 표기]}로 박음. 값 결손 축은 제외(정보부재≠악재). found_hint에 _LABEL_SYNONYMS(영문라벨→한글의미: unjustified→선반영/과대, monotonic_up→단조상승 등) + _num_hints(소수1·2자리만, 정수 제외=거짓통과 방지).
- (2) 코드 step_d7_select.py: selector 호출 후 selected 종목의 must_address가 D7_output에 인용됐는지 _check_coverage() grep → 미인용=재호출(1회), 재실패=파이프라인 차단(RuntimeError). 프롬프트에도 must_address 인용 의무 주입. portfolio-selector.md에 강제 안내 1줄.
- 검증 ✅: 6/18 D7_input 재빌드 → 삼성전자 must_address 11항목(f6 unjustified·f3 0.76·도메인 profile). 게이트밖 종목=None. 테스트1(실제 6/18 출력, f6 인용함)→통과하되 D2_today_phase=reaccel 1건 검출(실제로 selector가 차트 '오늘 국면'을 selected reasoning에 raw/의미 모두 미언급 = 진짜 검출). 테스트2(데이터 미인용 가짜 출력)→11건 미인용 재호출 트리거. import 정합 OK.
- ✅ 라이브 end-to-end 재호출 루프 검증 (S353): step_d7_select.run()을 임시 run_dir(6/18 복제)로 실행, call_agent monkeypatch로 3경로 확정 검증 — ①정상 루프: 1차 미인용(11건) 검출→[WARN]→재호출(누락항목 명시 프롬프트)→2차 인용→[OK] 통과, 총 2회 호출. ②차단: 재호출 후에도 미인용→RuntimeError 파이프라인 차단(총 2회 후 중단). ③프롬프트에 "재작성 필수"+누락항목 주입 확인. baseline 6/18 D7_output(7662B) 무손상, 임시 디렉토리 정리. 재호출 루프 로직 자체는 확정 작동. (nested LLM 실호출은 비결정·고비용이라 monkeypatch로 루프 검증 — 실 selector 통합은 다음 평일 라이브 run에서 자연 확인)
- ✅ f6·f3 처리 = 종결 (S353 PM 정리): 원래 "score 5축에 f6·f3 신설?" 질문이 출발점이었으나, PM이 더 나은 방향으로 대체: ①must_address로 selector가 f6·f3 반드시 인용·해석 강제 ②f6 자체를 forward 컨센서스로 제대로 만듦(정당화 가능성). score(rank_sum) 점수 축에 직접 넣는 건 안 함 — RS 중복(theme_rs=rs_high 계열→S345 편중 재발) + 감점철학 위배(S330 게이트폐기)라는 타당한 이유. selector 강제가 그 역할 대체. → 별도 score 축 신설 작업 없음(종결). (b) _LABEL_SYNONYMS 커버리지(새 profile 라벨 등장 시 보강)는 운영 중 자연 발생.
★★ (이전) 갱신 (S353 2026-06-21) — 복기 정정 + 근원결함 재진단 (PM 지적: "예측 빗나감≠확률, 쓰기로 한 게 안 쓰임=결함"):
1차 복기가 "6/18 시스템이 f6 선반영을 점수에 못 넣어 강도로 선정"이라 단정 → 코드/산출 검증으로 오류 판명(score_compose.py:138, runs/20260618/D7_output.md). 정정:
- (A) score(rank_sum) 레벨: rank 5축 = rs_high·amount·ofm·cat_raw·ea_z. f6 선반영(valuation_verdict)·f3 theme_rs는 점수 축이 아님(raw_metrics+gate_warning 텍스트로만). → rank은 선반영 무시한 순위. 이건 사실 — S351이 selector profile로만 보강하고 score 5축은 그대로 둠.
- (B) selector 레벨: 그러나 selector는 domain_reasoning.D6.profile.valuation_verdict="unjustified"를 실제로 사용. D7_output 관점B 전체가 f6 분석(PER 47.9·required 4.79x), 체크리스트 "[x] valuation_verdict 핵심 입력 사용", f3도 "SMH LEADING d20+14.71%" 인용. → "쓰기로 한 f6·f3가 안 쓰였다"는 selector 레벨에선 거짓. 복기가 score만 보고 selector 출력을 안 읽은 오류.
- (C) 진짜 결함 2개: ①score 5축에 f6·f3 부재 = rank이 selector를 "선반영 무시한 순위"에서 출발시킴(S351 미해결 — selector profile만 보강, 점수 축 미반영). ②selector가 강한 실적(ea_z high)으로 f6 unjustified를 상쇄 정당화하는 합성 경향(6/18 삼성전자). 패턴A "rank1 제침"은 정정: selector가 명시 대조 후 의도적 뒤집기(근거有).
- 정정 반영: 20260615_discover_retrospective.md §1·§2(6/18)·§5(처방1·2)에 [2026-06-21 정정]란. 복기-스코어러 교훈: 점수만 보지 말고 selector 출력(domain_reasoning 사용 여부)까지 읽어야 "데이터 미사용 vs 판단 갈림"을 구분. 🔴 잔여: discover-scorer.md 명세에 "score vs selector 레벨 분리 확인 의무" 추가 검토 + 5일 전수 재교차(f3·패턴A 다른 날도 같은 오류인지).
★ (이전) 갱신 (S353 2026-06-21) — 6/15주 discover 한주 복기(/rd) 실행:
collect_discover_retro.py --week-monday 2026-06-15 → 5일 day_packages(33/33/32/31/31종, 게이트통과 8/7/4/6/1) + retro/discover-scorer 1회 → docs/weekly_research/20260615_discover_retrospective.md. 데이터 정합성 검증 통과(6/18 삼성전자 f1 RS 0.50·f6 unjustified·required 4.79·D+1 -2.34 / 삼성생명 RS 0.71·f6 결손·+5.97 / SK하이닉스 r1 +2.94 — 산출물=실데이터 일치). 결과: 선정 forward 측정가능 3건 = SK하이닉스 6/16 +16.04·6/17 +9.64(둘 다 옳게 선정), 삼성전자 6/18 -2.34(결함일, f7=NEGATIVE). 6/15 선정無·6/19 PENDING(FDR결손). 결함 2종(전부 PROPOSED·검증임계 미달): ①패턴B "강도 다축(f4·f5) 켜짐 + f6 선반영 unjustified(멀티플 팽창끝)"를 강도 rank합으로 상위 → 6/18 삼성전자·6/15 후성 2회 forward 마이너스. SK하이닉스(같은 강도지만 f6 stretched)는 +로 이김 = 갈림은 f6 unjustified vs stretched. ②패턴C f1→f3 사슬(theme_n 크기 vs theme_rs 시장강도) 미반영 → 6/15 후성(theme_n=27·theme_rs≈0 죽은테마, -24.38) vs 삼성전기(theme_n=2·theme_rs+2.23 산테마, +13.56). 처방=관점 위원회(조합 고정, A다축강도 vs B선반영 집중 → forward 대조 관점B 우세). 🔴 별도 플래그: 6/17 진짜 최대승자(삼화콘덴서 +13.28·삼성전기 +11.71)는 D2=NEUTRAL로 차트게이트 사전 절단(기회손실 vs 변동성회피는 D+3/5 미도래로 미판정). 표본 한계: 명백 결함 2건(삼성전자+후성), 즉시 산식변경 금지. 별도 2주+ 반복 시 f6·f3 관점 위원회 신설 PM 결정.
★ (이전) 갱신 (S351 2026-06-15) — discover 복기→selector 재설계·DART 복구·우선주 제외:
discover 복기(retro/discover-scorer 신설)에서 "선정종목이 후보 중 패자였다" 발견 → 종목선정 파이프라인 근본 진단. 4개 결함 + 수정:
- (1) selector = 산식 rank1 거수기 → 재설계. portfolio-selector.md가 "rank1 의무 채택"을 강제했음. 수정: rank=1차 줄세움으로 강등, selector가 domain_reasoning(각 도메인 판단 서사)+4축 profile로 해석해 선정. "데이터 읽는 법" 섹션 신설(averaging 금지·각 도메인 읽는 법·결손=회피처 금지).
- (2) 상쇄(averaging) 구조 → rank_sum 더하기가 강축(오더플로·RS)으로 약축(실적·선반영) 묻음. 점수 압축이 "A약·B강" 프로파일을 평균으로 지움. 수정: build_d7_input이 축별 강약 profile 보존 주입(schemas.py D7CandidateRow.domain_reasoning 필드).
- (3) ea_z 수집 갭 → ea_panel 196종 고정(corp_code_map 197종)이라 discover universe 미커버 → ea_no_rows. 수정: expand_corp_code_map.py 신설(universe→맵 자동확장 197→240), DART 재수집(720k행/239종), ea_panel 196→239종, universe 커버 57→95%. README_dart_refresh.md(분기 갱신 운영가이드).
- (4) 우선주 도피 → selector가 결손(우선주 corp_code 미매핑)을 "덜 나쁨"으로 오용. 수정: step_universe.py 우선주 제외 필터(끝자리≠0). 6/09 universe 우선주 5종 제외, 단위테스트 8/8.
- 상세: docs/work_logs/2026-06-14_S351_retro_system_build_lens_fix.md §8~12.
- 🔴 잔여 1순위: 우선주 제외+4축 profile 적용 파이프라인으로 6/08·09·11 end-to-end 라이브 재검증(selected가 진 종목 실제 회피하는지). + obs2 4축 averaging 금지 미반영 + 선반영 처방 별도 표본 재현.
(이전) 최근 갱신 (S332 2026-05-30): ats_main theme 전수 검수 완료 — 네이버 공식 테마 + DART 교차로 791종 검증, themes[0] 변경 500건 + 레코드 제거 3건. industry: placeholder 354→21로 감축. 자세히는 docs/work_logs/2026-05-30_ats_theme_full_audit.md.
발견된 결손/오류 항목¶
A. OHLCV 컬럼명 대소문자 불일치 (★★ 영향 최대)¶
증상: data/backtest/s303/ohlcv/*.parquet 컬럼이 ['Open','High','Low','Close','Volume','Change'] 대문자인데 indicator 코드 다수가 "close" 소문자 하드코딩.
영향: - D2(차트) 80종목 중 79건 UNAVAILABLE (모든 블록 결손) - D5(수급) 14/80 결손 - D6(실적) 26/80 결손 - D8 첫 실행 시 KeyError 'close'
해결: ✅ 완료 (S329 2026-05-29)
- ✅ position_status.py — _col() 헬퍼
- ✅ build_d2/d3/d4/d5_input.py _load 진입점에 df.columns = [c.lower() ...] 일괄 정규화 → D2 차트 6블록·D5 수급 4블록 전부 복구 (010170 missing 0 확인)
- ✅ chart_indicators.compute_volume_profile Interval/category 비교 버그 동반 수정 (line 156)
- 검증: 010170 accel_ratio −0.35(과열 꺾임), 삼성전기 +1.83(가속) 정상 산출
B. ats_main 테마 첫 번째 순서 검수 필요¶
증상: 010170 대한광통신 → themes[0]="방위산업"이지만 DART 사업보고서 매출 비중 0건 (통신49.5%/전력50.5%, 방위 미분류).
영향: D4 primary_theme 잘못 분류 → 매크로·재료 매칭 시 잘못된 키워드 사용 → 리포트 신뢰도 저하.
해결: ✅ 완료 (S332 2026-05-30) - 791종 전수 검증 (네이버 공식 테마 762/791 커버 + DART business_summary 교차) - 8 에이전트 병렬 진입검증 (기준: 매출 주력 OR 실질 진입. R&D·MOU·예정 불인정) - themes[0] 변경 500건 + 레코드 제거 3건 (코드-이름 오염: 삼정펄프에 "한수원" 등) - MLCC 테마 신설 (삼성전기·삼화콘덴서) - industry: placeholder 354 → 21건 (PM 지시 "네이버 테마에 매칭" — 4차 사전 매칭) - 산출: data/ats_main_v2.json, data/naver_code2themes.json, data/audit_full_result{1-8}.json, scripts/audit/*.py - 작업 기록: docs/work_logs/2026-05-30_ats_theme_full_audit.md
작업 (구):
1. ats_main_v2의 모든 종목에 대해 DART 매출 세그먼트 vs themes[0] 일치 검증 스크립트 작성
2. 불일치 종목 자동 정정 (매출 50%+ 사업 = primary)
3. data/ats_main_v2.json 변경 이력 관리 (git diff)
C. score_compose 단순 z 합산의 한계¶
증상: 010170 final_score=3.788 중 stock_rs_z=+4.735가 단독 견인. 그러나 D3 reasoning은 "단기 RS 음전환·20d DD -20%·monotonic 0.525 → NEUTRAL". 단순 z 합산이 verdict 의미를 못 살림.
영향: 발굴 종목이 RS 단일 신호로 뽑힐 위험. v6 백테스트는 multi-axis 동시 충족 가정인데, 현재 산식은 한 축 극단으로도 통과.
해결: ✅ 완료 (S329 2026-05-29) — score_compose.py 전면 재작성:
- 버그1: rs_vs_theme placeholder(하드코딩 0) → 제거. 테마는 z축 아닌 theme_bonus(집결도 역산)로.
- 버그2: 펀더멘탈 곱셈 소멸 → ea_z 합산축으로.
- 버그3: 결손 0치환 → z 모집단 제외(UNAVAILABLE/UNKNOWN).
- 버그4: RS 단독 통과 → 과열 게이트(catalyst_overheat / single_axis_extreme / positive_axis_lt2).
- 신규: catalyst_z 5번째 축 (재료) + theme_bonus(강한종목 집결도 역산, RS 1회).
- 검증: 010170 1위→9위(과열차단), 삼성전기 selector 선정. selector 명세에 게이트 해석 추가.
- 설계: docs/planning/score_compose_catalyst_redesign.md + theme_grouping_inversion_redesign.md
D. 재료(catalyst) 자동 매칭 실패¶
증상: evening material-grader / theme-mapper 출력에 010170 관련 항목 0건 → step_report.py 재료 섹션 "자동 매칭 결과 없음" 표기.
해결: ✅ 완료 (S329 2026-05-29) — news_curator(구시스템) 폐기 확인. 살아있는 리포트로 전환:
- scripts/discover/indicators/catalyst_parser.py 신규 — docs/daily_reports/{date}_post_market.md UNIT5(상한가 WHY)/UNIT6(신규발견) 표 파싱.
- 종목코드별 {재료강도(hard/soft/none), verdict(TRACK/WATCH/NOISE), 과열플래그} 추출.
- catalyst_raw → score_compose catalyst_z 축. 010170 검출: soft·WATCH·과열 → raw −0.7.
- 두 형식 코드 추출 지원: "대한광통신 (010170)" / "010170 대한광통신".
E. D8 today_d1_verdict 결손 → regime_shift 평가 불가¶
증상: D8 builder의 compute_regime_snapshot()이 today_d1_verdict를 row.get(...)로 받는데 positions.json에 그 값 없음 → null → regime_shift 평가 부정확.
해결: orchestrator(run_discover.py)가 D1 phase 완료 후 D8 phase 진입 전, 현재 D1 verdict를 모든 positions의 today_d1_verdict에 자동 주입.
F. D8 PnL 0% — OHLCV 데이터 최신성¶
증상: 010170 OHLCV 마지막=2026-05-22. T0=2026-05-27이지만 5일 갱신 없음 → entry_date=2026-05-22 = t0 데이터 → current_ret=0%.
해결: 1. 데일리 OHLCV 자동 갱신 hook (장 마감 후 fetch) 2. 또는 D8 builder가 "최신 가용 거래일"을 t0_effective로 사용하고 "데이터 stale" 경고
G. chart_warning 미산출¶
증상: D8 today_snapshot.chart_warning이 항상 빈 dict (ofm_bvc_5bar_low/vp_up_down_ratio_log_low/cvd_z_low 모두 false 기본값).
해결: position_status.compute_today_snapshot()이 chart_indicators 호출해 실제 신호 산출하도록 보강.
H. material_alive / flow_pattern / flow_5d_direction 자동 산출¶
증상: D8 4조건 중 C2(재료)/C3(수급)이 항상 null로 결손.
해결:
- C2 material_alive: events_since_entry 자동 수집 (news-radar 연계)
- C3 flow_pattern/flow_5d_direction: data/flow_series/{code}.json 또는 SEN-1 호출
I. D9 history dump의 selected_code 추출 정확도¶
증상: D7 output.md에서 selected_code를 regex로 파싱하는데 에이전트가 다른 포맷으로 출력 시 누락 가능.
해결: - D7 envelope JSON에 selected.code 표준 필드 강제 - step_d7_select가 envelope 추출 시점에 정규화
J. theme_strong trigger 0건¶
증상: trigger union에서 theme_strong이 항상 n=0.
원인: data/theme_tracker/dashboard.json의 leading 키 구조와 trigger 코드의 fallback 매칭 미스.
해결: dashboard.json 스키마 표준화 또는 trigger 모듈의 매칭 키 확장.
K. D1 매크로 "변화 중심" 재설계 ✅ 완료 (S329 2026-05-29)¶
배경: D1이 스팟/z 1점만 제공 → 자금 이동·가속/감쇄·섹터쏠림 판단 불가 (010170 사후진단 중 발견).
해결:
- ChangeSet(spot/d1/d5/d20/pct/z60) — 금리·환율·상품·지수·선물OI 전반에 다기간 변화 부착.
- derivatives 보강: 선물4종 OI ChangeSet + 옵션 put/call OI 비율.
- D1FxCommodityBlock 신규(dxy/usd_krw/wti/gold/copper).
- D1CotFlowBlock 신규 — CFTC COT 글로벌 큰손 포지션(spx/ndx/jpy/eur, am_net/lev_net/52w z).
- macro-judge.md 해석 규칙 6개(자금이동/가속감쇄/섹터쏠림/선물OI/옵션헤지/COT).
- 설계: docs/planning/d1_change_centric_redesign.md. 보류: deriv_sent_z(소스 빈컬럼), FRED 3개(DGS3MO/DTWEXBGS/DEXKOUS).
L. ats_main themes[0] 정확도 (재부각)¶
증상: S329 검증 중 삼성전기(009150) primary_theme이 "2차전지(전고체)"로 매핑 (MLCC여야). theme_concentration·D4 verdict에 영향.
영향: theme_bonus·집결도 계산이 잘못된 테마로 그룹핑. 단 catalyst·RS가 순위 견인이라 결과 영향은 제한적.
해결: B(ats_main 전수 검수)와 통합. DART 매출 세그먼트 vs themes[0] 일치 검증 + 정정.
M. theme_concentration 소수표본 (부분 해결)¶
증상: universe 내 테마 멤버 1개면 집결도 1.0(거짓 만점) → theme_bonus +1.5 과다.
해결: ✅ MIN_MEMBERS_FOR_CONC=2 가드 — 멤버 2개 미만이면 고집결 보너스 금지(저집결 +0.3). 단 표본 자체를 늘리려면 universe 확대 or dashboard.json 멤버 병합 필요(잔여).
우선순위 (S330 진입 시)¶
| # | 항목 | 영향 | 난이도 |
|---|---|---|---|
| ~~1~~ | ~~A. OHLCV normalize~~ | ✅ S329 완료 | — |
| ~~2~~ | ~~C. score_compose~~ | ✅ S329 완료 | — |
| ~~3~~ | ~~D. 재료 매칭~~ | ✅ S329 완료 (리포트 파싱) | — |
| ~~4~~ | ~~K. D1 변화중심~~ | ✅ S329 완료 | — |
| 1 | E. today_d1_verdict 자동 주입 | ★★ (D8 정확성) | 낮음 |
| 2 | F. OHLCV 데일리 갱신 | ★★ (운영 필수) | 중간 |
| 3 | G. chart_warning 산출 | ★★ (D8 안전장치) | 중간 |
| 4 | H. C2/C3 자동 산출 | ★★ (4조건 완성) | 중간 |
| ~~5~~ | ~~B+L. ats_main theme 전수 검수~~ | ✅ S332 완료 (500건 변경, 21 잔여) | — |
| 6 | J. theme_strong trigger 정상화 | ★ | 낮음 |
| 7 | I. D7 selected 파싱 표준화 | ★ | 낮음 |
| 8 | M. 집결도 표본 확대 | ★ (잔여) | 중간 |
작동 확인 완료 (2026-05-27 첫 운영)¶
✅ orchestrator 8 phase 강제 순회 + _enforce_required 게이트
✅ 80종목 × 5 에이전트 = 400 호출 완료 (~4시간)
✅ D7 selected 산출 (010170)
✅ D8 positions 등록 시 추적 리포트 정상 산출 (WARN status, 4조건 매트릭스, alpha_path, regime, 시각전환 신호)
✅ D9 cold start PENDING 처리
✅ history dump (D9 내일 입력)
✅ 리포트 docs/discover/{date}.md (6도구 + 재료 + 부록 구조)
✅ ats_main 010170 정정 (방위→광통신)
✅ position_status.py 컬럼 자동 감지
관련 문서¶
- 설계:
docs/work_logs/2026-05-27_S327_discover_v4_atomization.md - orchestrator:
docs/work_logs/2026-05-27_S328_discover_orchestrator.md - 첫 리포트:
docs/discover/20260527.md - 명세:
.claude/agents/discover/*.md(9개) - 코드:
scripts/discover/{steps,triggers,builders,indicators}/*.py