뉴스 시그널 TDD
Background
PRD 참조. 뉴스 감성 + 기술 추세를 종합한 참고 시그널을 별도 Python FastAPI 서비스로 제공한다. 코어(Kotlin)와 분리한 이유는 ADR-201 참조.
Overview
서비스: signal-service (FastAPI, :8000). Python 3.11+, httpx, claude CLI.
흐름: 토스 종목 판별 → 뉴스 수집 → 감성 → 캔들 추세 → 블렌딩 → 캐시.
Terminology
PRD를 따른다.
Define Problem
AS-IS
TO-BE
모듈형 파이프라인(news → sentiment → momentum → signal)으로 종합 점수 산출.
Possible Solutions
방안 비교
방안 설명 채택 미채택 사유 별도 Python 서비스 ML·뉴스 생태계 활용, FastAPI ✅ — Kotlin 코어에 통합 단일 배포 ✗ Python 뉴스·감성 도구 활용성↓, 코어 결합도↑ 감성=외부 LLM API ANTHROPIC_API_KEY 직접 호출 ✗ 사용자 지시로 로그인된 claude -p CLI 사용(키 미사용)
Detail Design
종합 공식
composite = clamp(0.6 × sentiment + 0.4 × momentum, -1, 1)
label:
≥ 0.5 강한 긍정
≥ 0.15 긍정
-0.15~0.15 중립
> -0.5 부정
≤ -0.5 강한 부정
모멘텀 (3요소 평균)
요소 계산 정규화 MA20 이격 (close − MA20)/MA20 ÷ 0.10 ±10% = ±1 MA5/MA20 크로스 (MA5 − MA20)/MA20 ÷ 0.05 (데이터 ≥40) ±5% = ±1 RSI14 (RSI − 50)/50 (데이터 ≥15) 50 중심 0
감성 (claude -p)
헤드라인 수집 (0건 → 즉시 중립).
claude -p "<프롬프트>" (timeout 120s). 프롬프트 지침: 실적·수주·규제·신제품 가중, JSON만.
{"score": -1~1, "label": ...} 파싱 → 클램핑. 파싱 불가 → NEUTRAL(0.0).
Sequence Diagram
sequenceDiagram
participant C as Client
participant M as main.py
participant Ca as TtlCache
participant T as toss_client
participant N as news
participant S as sentiment(claude -p)
participant Mo as momentum
C->>M: GET /signals/{symbol}
M->>Ca: get(symbol)
alt 캐시 miss
M->>T: get_stock / get_closes
M->>N: fetch(headlines)
M->>S: score(headlines)
M->>Mo: score(closes)
M->>M: combine(0.6:0.4) + classify
M->>Ca: set(symbol)
end
M-->>C: Signal
응답 필드
symbol, name, market(KR/US), currency, sentiment_score, sentiment_label, momentum_score, composite_score, label, last_close, change_5d, headlines[], generatedAt.
ERD
별도 영속 스토리지 없음 (stateless + in-memory 캐시). watchlist는 BE(GET /api/v1/watchlist)에서 조회.
Testing Plan
signal.py: 0.6:0.4 공식 검증, 5단계 라벨 임계값 경계값 확인, [-1,1] 클램핑 동작 검증.
sentiment.py: 정상 JSON 파싱, 파싱 불가·타임아웃·헤드라인 0건 시 NEUTRAL(0.0) 폴백 검증.
momentum.py: 3요소 정상 계산, 데이터 부족(MA20/크로스/RSI 누락) 처리, 클램핑 동작.
cache.py: hit 시 재계산 미발생 확인, TTL 만료 후 miss 전환, 종목 키 독립성.
toss_client.py: 정상 토큰 발급, 401 재발급 후 재시도 성공, 연속 실패 예외 전파.
외부 의존(claude CLI, 토스 API, 뉴스 RSS)은 모두 unittest.mock.patch로 처리한다.
Release Scenario
cd ml && uv run uvicorn app.main:app --port 8000.
환경변수(TOSS_API_KEY/SECRET, BACKEND_BASE_URL, SIGNAL_CACHE_TTL, NEWS_LIMIT) 주입.
전제: 로그인된 claude CLI.
롤백: ML 서비스 중단 시 FE 시그널 탭만 비활성, BE/알림은 독립 동작.
Observability
관측 지표
지표 측정 방법 임계값 claude -p 호출 시간 subprocess 실행 시간 로그 >10s 경고 감성 파싱 실패율 NEUTRAL 폴백 횟수 / 전체 호출 >20% 경고 캐시 hit/miss 요청별 로그 (INFO: cache hit <symbol>) — 뉴스 수집 실패 빈 헤드라인 반환 횟수 — API 응답 시간 콜드 vs. hit 비교 콜드 >10s 경고
알람 조건
claude -p timeout (120s) 발생 시 WARNING 레벨 로그.
토스 API 401 재발급 3회 연속 실패 시 ERROR 레벨 로그.
GDELT + Google 뉴스 모두 실패(빈 헤드라인) 시 WARNING 레벨 로그.
로그 포인트
sentiment.py: 호출 시작·완료·실패(파싱 오류/타임아웃)
toss_client.py: 401 재발급 시도·성공·실패
news.py: GDELT 폴백 발생
cache.py: hit(cache hit {symbol})·miss(cache miss {symbol})·만료(cache expired {symbol})
FE 영향 분석
FE 시그널 탭은 ML(:8000) 직접 호출(NEXT_PUBLIC_SIGNAL_BASE_URL), BFF 미경유.
watchlist는 BE(:8080) 호출로 분리.
항목 내용 프로젝트 주식앱 — 뉴스 시그널 담당자 1인 (biuea) 서비스명 signal-service (FastAPI, :8000)스택 Python 3.11+, FastAPI, httpx, uv, claude CLI 대상 티켓 STK2-01 ~ STK2-08
Document History
날짜 변경 내용 작성자 2026-06-19 초안 작성 (Background~Release Scenario) biuea 2026-06-19 Observability 상세화, Project Information·Document History 추가 biuea 2026-06-21 스킬 포맷 정비 (Testing Plan bullet 변환, Phase 표현 제거) biuea
관련 문서