TDD — 관심종목 고도화
Background
stock-application 모노레포. BE = Kotlin/Spring(:8080), ML = Python FastAPI(:8000), FE = Next.js(:3000). 관심종목 뉴스 시그널을 구축했으나 헤드라인이 제목 문자열만 반환된다. 뉴스 링크·번역·per-headline 감성·요약·예측 차트를 추가해 카드 정보 밀도를 높인다.
Overview
4개 변경 축:
- ML 뉴스 파이프라인 고도화:
Headline데이터 클래스(제목·URL·번역제목·감성 레이블) 반환, 전체 요약 추가 - ML 예측 API: 기술적 지표 기반 단기(5일) 예측 곡선 엔드포인트
- BE 뉴스 스냅샷 영속화:
news_snapshots테이블, 조회 API - FE UI 개선: 감성 레이블 직관화, 뉴스 링크+배지, 세부 페이지+차트
Terminology
| 용어 | 설명 |
|---|---|
| Headline | 뉴스 항목 1건 — 제목·URL·번역 제목·호악재 레이블 포함 |
| 뉴스 스냅샷 | 특정 시점 심볼의 뉴스 헤드라인 목록 + 요약 — BE DB에 영속화 |
| 예측 곡선 | 기술적 모멘텀(MA·RSI) 기반 단기 가격 방향 참고 시그널 |
| 호재/악재 레이블 | 개별 헤드라인의 투자 방향성 분류 |
| 행동 레이블 | 종합 시그널을 “매수 유망/상승 주목/중립 관망/하락 주의/매도 주의”로 표현 |
Define Problem
AS-IS
Signal.headlines: list[str] # 제목만, URL 없음
signal.label: "강한 긍정" | "긍정" | "중립" | "부정" | "강한 부정" # 행동 의미 불명확
뉴스 요약: 없음
해외 뉴스: 영어 그대로 노출
per-headline 감성: 없음
예측 차트: 없음 (백테스트 요약만 있음)
TO-BE
Signal.headlines: list[Headline] # {title, url, translated_title, sentiment}
Signal.news_summary: str # "전반적으로 수익 개선 기대감이 높고 공급망 우려는 제한적"
signal.label: "매수 유망" | "상승 주목" | "중립 관망" | "하락 주의" | "매도 주의"
GET /predictions/{symbol} → {historical, predicted, note}
BE: news_snapshots 테이블 영속화
FE: 세부 페이지 /watchlist/[symbol]
Possible Solutions
방안 비교
| 방안 | 설명 | 왜 채택 | 미채택 대안 |
|---|---|---|---|
| per-headline 감성 (클로드 1회 배치) | 전체 헤드라인을 claude -p 한 번 호출로 per-item 분류 + 요약 동시 추출 | 기존 claude -p 패턴 재사용, API 호출 1회 | 각 헤드라인별 개별 호출(느림·비용 多) |
| 번역 (클로드 통합) | per-headline 감성 프롬프트에 번역 요청 포함 | 추가 API 호출 없이 처리 | Google Translate API(유료) |
| 예측 곡선 (선형회귀 + 변동성 밴드) | 최근 20일 종가 → 선형회귀 기울기 + 모멘텀 방향 가중 → 5일 예측, std로 밴드 | 외부 ML 라이브러리 불필요, 결정론적, 설명 가능 | LSTM(복잡도 높음·오버피팅 위험) |
| BE 뉴스 영속화 (Watchlist 도메인 추가) | news_snapshots 테이블, WatchlistItem과 1:1, TTL 기반 stale 여부 | 기존 watchlist 도메인 확장, 심플 | 별도 서비스 분리(불필요한 복잡도) |
Detail Design
감성 레이블 매핑
| 기존 | 변경 | composite 범위 |
|---|---|---|
| 강한 긍정 | 매수 유망 🟢 | ≥ 0.5 |
| 긍정 | 상승 주목 | ≥ 0.15 |
| 중립 | 중립 관망 | > -0.15 |
| 부정 | 하락 주의 | > -0.5 |
| 강한 부정 | 매도 주의 🔴 | < -0.5 |
Component Diagram
flowchart LR subgraph FE["FE (Next.js)"] WIC["WatchlistItemCard\n(클릭→상세)"] DP["watchlist/[symbol]\n(세부 페이지)"] PC["PredictionChart"] NI["NewsItem\n(배지+링크)"] end subgraph BE["BE (:8080)"] WAC["WatchlistApiController"] GNSU["GetNewsSnapshotUseCase"] SNSU["SaveNewsSnapshotUseCase"] NSDS["NewsSnapshotDomainService"] NSR["NewsSnapshotRepository\n(interface)"] NSRI["NewsSnapshotRepositoryImpl"] end subgraph ML["ML (:8000)"] SIG["GET /signals/{symbol}"] PRED["GET /predictions/{symbol}"] AH2["analyze_headlines_v2\n(claude -p)"] BP["build_prediction\n(선형회귀)"] end WIC --> DP DP --> SIG DP --> PRED DP --> WAC SIG --> AH2 PRED --> BP WAC --> GNSU WAC --> SNSU GNSU --> NSDS SNSU --> NSDS NSDS --> NSR NSR -.->|implements| NSRI
Sequence Diagram — 세부 페이지 로드
sequenceDiagram participant U as User participant FE participant BE as BE :8080 participant ML as ML :8000 U->>FE: 관심종목 클릭 FE->>BE: GET /api/v1/watchlist/{symbol}/news FE->>ML: GET /signals/{symbol} FE->>ML: GET /predictions/{symbol} BE-->>FE: NewsSnapshot (요약+헤드라인) ML-->>FE: Signal (행동 레이블) ML-->>FE: Prediction (historical+predicted) FE->>U: 차트 + 뉴스 요약 + 시그널 렌더
예측 곡선 알고리즘
입력: closes[N] (최근 20일 종가)
1. 선형회귀: slope = (closes[-1] - closes[-5]) / 5 # 5일 기울기
2. 모멘텀 가중: adjusted_slope = slope * (1 + momentum_score)
3. 예측가: p[d] = closes[-1] + adjusted_slope * d (d=1..5)
4. 밴드: σ = std(closes[-20:])
upper[d] = p[d] + 1.5 * σ
lower[d] = p[d] - 1.5 * σ
주의문구: "기술적 모멘텀 기반 참고 시그널 — 투자 판단 근거로 사용하지 마십시오"
ML claude -p 통합 프롬프트 (analyze_headlines_v2)
다음은 {market} 종목의 최근 뉴스 목록이다(URL·제목 쌍).
각 헤드라인에 대해:
1. 한국어 번역 제목 (KR이면 원문 유지)
2. 호악재 분류: "호재" | "악재" | "중립"
그리고 전체 헤드라인 기반 1줄 한국어 요약.
JSON만 답하라. 형식:
{
"summary": "...",
"items": [{"title": "원문", "translated_title": "번역", "sentiment": "호재|악재|중립"}]
}
ERD
erDiagram watchlist_items ||--o| news_snapshots : has watchlist_items { bigint id PK varchar symbol } news_snapshots { bigint id PK varchar symbol UK text summary json headlines datetime(6) refreshed_at datetime(6) created_at }
headlines 컬럼 JSON 구조:
[{"title":"...", "url":"...", "translated_title":"...", "sentiment":"호재"}]Testing Plan
- ML:
analyze_headlines_v2프롬프트 파싱 및 JSON 응답 구조 검증 (pytest + mock) - ML:
build_prediction수치 검증 — 방향 일치·밴드 포함 여부·빈 입력 예외 (pytest) - BE domain:
NewsSnapshotDomainService.isStale1시간 경계 판단 (Kotest BehaviorSpec + MockK) - BE application:
GetNewsSnapshotUseCase신선/stale 분기·ML 실패 시 graceful degradation (Kotest + MockK) - BE infrastructure:
NewsSnapshotRepositoryImplsymbol 기준 upsert (Kotest + Testcontainers MySQL) - BE presentation:
GET /api/v1/watchlist/{symbol}/news응답 구조·404 케이스 (Kotest + MockMvc) - FE:
PredictionChart실선/점선/밴드 렌더,NewsItem배지·링크,SignalLabel5단계 매핑 (Vitest)
Release Scenario
- ML 배포:
analyze_headlines_v2적용 (기존 헤드라인 타입 변경 — FE 배포 전 호환 필요) - BE 배포: Flyway 마이그레이션 후 배포
- FE 배포: 새 Signal 타입 + 세부 페이지
롤백: ML Signal 응답 shape 변경이 Breaking Change이므로 FE 배포 전까지 /signals v1 응답 유지 또는 FE와 동시 배포.
Project Information
- 담당: biuea
- 기간: ~1주
Document History
| 날짜 | 변경 내용 | 작성자 |
|---|---|---|
| 2026-06-19 | 초안 작성 | Claude |
| 2026-06-21 | 스킬 포맷 정렬 | Claude |