소개
시도하고자 했던 것과 그 이유
주식 투자를 하다 보면 상승장 후반부에 항상 같은 질문에 맞닥뜨립니다. "지금 들고 있는 종목, 계속 가져가도 되나?"
주가지수는 사상 최고치를 경신하고 있는데, 내부를 들여다보면 상승에 참여하는 종목 수가 갈수록 줄어드는 국면이 있습니다. 이른바 시장 폭(breadth, 브레드스)이 좁아지는 신호입니다. 역사적으로 이 신호는 고점 형성보다 수개월에서 수 년 앞서 나타나기도 했고(2000년 나스닥 약 2년 전, 2007년 S&P 수개월 전), 보유 종목 리밸런싱 타이밍을 잡는 데 중요한 단서가 됩니다.
문제는 이 판단을 매번 수작업으로 한다는 것이었습니다. S&P 500 503개 종목, KOSPI 800여 개 종목의 이동평균선 위/아래 비중, A/D 라인(Advance/Decline Line) 추이, 신고가·신저가 비중을 일일이 계산하고, 거기에 보유 종목의 이익추정 방향·상대강도(RS)·밸류에이션 z-score까지 엮어서 판단을 내리려면 시간이 너무 많이 걸렸습니다.
그래서 데이터 수집부터 판정 출력까지 전 과정을 자동화하는 도구를 만들어보고 싶었습니다. 개발 경험이 없는 투자자가 Claude Code와 함께 실무 투자 도구를 처음부터 만들어 본 실험입니다.
---
진행 방법
사용 도구
- Claude Code (claude.ai/code 데스크톱 앱) — 메인 개발 파트너
- Python + Streamlit — 앱 프레임워크
- yfinance — 가격 데이터 (15개월치)
- FinanceDataReader — S&P 500 / KOSPI 구성종목 목록
- pykrx — 한국 종목 PER·EPS 자동 수집
- Plotly — 차트
- VS Code — 편집기 및 통합 터미널
---
첫 번째 프롬프트: 요구사항 정의
처음에는 무엇을 만들어야 하는지를 Claude Code에 설명하는 것부터 시작했습니다. 개발 명세서를 쓰듯 요구사항을 정리해 첫 프롬프트로 넘겼습니다.
S&P 500 구성종목(FinanceDataReader) + yfinance 15개월 종가를 받아서 아래 브레드스 지표를 시계열로 계산하는 Streamlit 앱을 만들어줘.
[계산 항목]
1. 200일 이동평균선 위 종목 비중 (pct200)
2. 50일 이동평균선 위 종목 비중 (pct50)
3. 누적 A/D 라인 (일별 상승 종목 수 − 하락 종목 수 누적)
4. 52주 신고가 비중, 신저가 비중
5. 레짐 점수 = mean(pct200, pct50, 신고가비중) — 다이버전스 시 −15
6. 다이버전스 판정: 지수 상대위치 ≥0.9 AND A/D 상대위치 ≤0.8
[레짐 3등급]
- 60점 이상: 건강 (초록)
- 35~60점: 축소 전환 (노랑)
- 35점 미만: 협소·약화 (빨강)
[유니버스]
- 미국: S&P 500 구성종목만.
- 한국: KOSPI 구성종목만, KOSDAQ 제외 (A/D 라인 왜곡 방지).
결과는 Plotly 차트로, Streamlit에 표시해줘.
Claude Code는 이 요구사항을 바탕으로 약 300줄짜리 초기 코드를 생성했습니다. 여기서 중요한 시행착오가 있었는데, KOSDAQ을 포함한 전 종목으로 계산했을 때 한국 A/D 라인이 0.0으로 찍히는 버그처럼 보이는 현상이 발생했습니다. 알고 보니 KOSDAQ 미세·신규 종목이 거래일수 기준을 충족하지 못해 A/D 계산을 왜곡하는 것이었습니다. KOSPI만 쓰도록 유니버스를 제한하자 정상화됐습니다.
---
판정 엔진 프롬프트: 보유/비중 축소/교체 로직
브레드스 계산이 안정화된 뒤, 핵심인 종목 판정 엔진을 추가했습니다.
판정 엔진을 추가해줘.
사용자가 보유 중인 종목과 교체 후보 종목을 각각 입력하면, 아래 3축 기준으로 판정을 내려줘.
[3축 판정 기준]
1. 펀더멘털: 이익추정 방향(상향/보합/하향) + 상대강도(RS) 상태(유지/둔화/붕괴)
2. 밸류에이션: PER z-score (최근 2년 기준)
- 미국: 직접 입력
- 한국: pykrx로 자동 계산, 실패 시 직접 입력으로 폴백
3. 비중 집중도: 소규모 / 핵심 비중 / 과도 집중 3단계 선택
[판정 결과]
- 보유: 이익추정 상향 + RS 유지
- 비중 일부 축소: 이익추정 상향이지만 밸류 z ≥1.0 또는 과도 집중
- 비중 축소: 이익추정 하향 또는 RS 붕괴
- 교체 검토 가능: 교체 후보가 '이익추정 상향 전환·카탈리스트·RS 바닥' 중 2개 이상
- 가치 함정 위험: 교체 후보 조건 1개 이하
"초과 X%p"는 쓰지 말고, "현재 비중이 과도 집중이면 비중 축소 검토"처럼 사용자 판단 기반으로 서술해줘.
이 단계에서 가장 많이 반복 수정을 했습니다. 처음에는 "목표 비중 − 현재 비중 = 초과분"이라는 임의 수치 기반 로직이 들어가 있었는데, 목표 비중이 사람마다 다르기 때문에 앱이 임의 숫자를 제시하면 오히려 혼란스럽습니다. 비중 입력을 숫자가 아닌 3단계 선택(소규모/핵심 비중/과도
집중)으로 바꾼 것이 실용성을 크게 높인 결정이었습니다.
---
앱·리포트 동일성 원칙 수립
결과물을 공유하려면 스크린샷이 아니라 단독으로 열 수 있는 HTML 파일이 필요했습니다. 그래서 공유용 단일 HTML 리포트 다운로드 기능을 추가했는데, 여기서 또 다른 문제가 생겼습니다.
앱 화면과 HTML 리포트의 차트 Y축 범위, 판정 문구, 색상이 달라지는 현상이 반복됐습니다. Claude Code와 함께 이 문제를 잡기 위해 "앱과 리포트는 항상 동일해야 한다"는 불변 원칙을 CLAUDE.md 파일에 명문화했습니다.
불변 원칙을 CLAUDE.md에 추가해줘:
"앱 화면과 HTML 리포트의 내용·차트·축·문구는 동일해야 한다."
이 원칙을 지키기 위해 reportfig() 함수 하나로
앱(st.plotly_chart)과 리포트(HTML 임베드) 양쪽에 동일 결과를 써라.
CLAUDE.md는 이 프로젝트에서 가장 중요한 파일이 됐습니다. 새 대화를 시작할 때마다 Claude Code가 이 파일을 먼저 읽고 이전 작업을 이어가기 때문에, 작업 이력·금지 용어·판정 로직을 모두 여기에 기록해뒀습니다.
---
CLAUDE.md 작업 이력 섹션 (실제 사용 예시)
## 작업 이력 (Work Log)
> 새 대화를 시작할 때 이 섹션을 먼저 읽어 이전 작업을 이어간다.
> 작업이 끝날 때마다 Claude Code가 이 섹션을 업데이트한다.
### 2026-05-30
완료된 작업
- 한국 PER z-score: 네이버 EPS 수집 → yfinance 주가 ÷ Forward EPS로
2년치 PER 시계열 재구성 → z-score 자동 계산.
- 섹터별 시장 폭 분해: get_sector_map(), compute_sector_breadth(),
sectorbar_fig() 구현. 앱·리포트 동일 적용.
- 이익추정 방향 자동판별: detect_rev_trend() — 네이버 EPS를
eps_history.json에 저장, ±2% 임계로 상향/보합/하향 판별.
진행 중 / 다음 작업
- 판정 매트릭스 재설계
- 레짐 협소 + 교체 가능 동시 발생 시 메시지 모순 해소
---
최종 앱 구조
약 5회의 세션, 누적 약 1,500줄 규모로 완성된 앱의 주요 섹션입니다.
① 용어 풀이 — 레짐 3등급·A/D 라인·다이버전스·상대위치·RS·밸류 z·비중 축소·가치 함정 정의
② 목적 및 판정 기준 — 언제 어떤 신호를 어떻게 해석할지 설명
③ 시장 폭 진단 — 참여도 추이(0~100% Y축 고정), 지수 vs A/D 라인 정규화 비교, 섹터별 브레드스 분해
④ 동일가중 vs 시총가중 비율 — 미국·한국 나란히, 공통 Y축
⑤ 판정 엔진 — 보유 종목·교체 후보 입력 → 3×4 매트릭스로 종합 권고 출력
---
결과와 배운 점
배운 점과 꿀팁
1. CLAUDE.md가 장기 프로젝트의 핵심이다
대화가 누적될수록 컨텍스트가 유실됩니다. CLAUDE.md에 판정 로직을 "DECISION LOG — 함부로 바꾸지 말 것"으로 명시하고, 완료된 작업·진행 중인 작업을 매 세션마다 업데이트하는 루틴이 생기자 새 대화를 시작해도 이전 맥락이 그대로 이어지는 효과가 났습니다.
2. 금지 용어 목록을 명시하면 문체가 일관돼진다
내가 원하는 표현과 쓰지 말아야 할 표현을 표로 정리해두면, Claude Code가 매번 같은 실수를 반복하지 않습니다. 자신만의 표현 원칙이 있는 분이라면 꼭 써보길 권합니다.
3. "항상 동일해야 한다"는 불변 원칙을 코드 구조와 명세서 두 곳에 동시에 박아둬야 한다
Plotly 차트를 앱과 HTML 리포트에 모두 쓰는 경우, Y축 범위나 색상이 슬금슬금 달라집니다. 원칙을 코드 구조(_report_fig() 단일 함수)와 CLAUDE.md 두 군데에 동시에 기록해두는 것이 핵심이었습니다.
---
시행착오
- KOSDAQ 포함 시 한국 A/D 라인 0.0 현상: 미세·신규 종목 때문. KOSPI만 쓰도록 유니버스 제한으로 해결.
- S&P 500이 503개인 버그 의심: 알파벳(GOOGL/GOOG), 폭스, 뉴스코프 등 복수 클래스 주식이 별도 티커로 존재하기 때문. 버그가 아니라 데이터
특성. 앱에 설명을 달아뒀습니다.
- pykrx KRX 조회 제한: pykrx가 KRX 무료 조회를 막히면 예외가 아니라 빈 DataFrame을 반환합니다. 빈 결과를 감지해 "KRX가 최근 pykrx 무료 조회를 제한하고 있습니다 — 직접 입력으로 전환합니다"라는 안내를 띄우는 폴백 로직을 추가했습니다.
- 스마트 따옴표 SyntaxError: 문서 편집기에서 프롬프트를 작성할 때 ' 대신 '(스마트 따옴표)가 섞여 들어가 파이썬 문법 오류가 난 경우가 있었습니다. 코드는 항상 VS Code에서 직접 확인하는 습관이 중요합니다.
---
앞으로의 계획
- 장기 시계열(1998년~) 동일가중 vs 시총가중 차트: 엑셀 데이터를 준비해 data/ 폴더에 올리면, 앱이 자동으로 yfinance 최신 데이터와 이어 붙여 전체 시계열을 완성하는 구조로 구현 예정.
- 등락비율(McClellan Oscillator) 추가: A/D 라인의 단기 모멘텀을 더 정밀하게 잡기 위해.
- 포트 일괄 판정: 여러 보유 종목을 CSV로 올리면 한 번 에 보유/축소/교체 판정이 나오는 기능.