IT 기술/AI

BriefStock 개발일지 #5 - 실사용이 드러낸 3가지 함정 — yfinance 지연, LLM JSON 중첩, 캐시 동시성 (Phase 7)

keun90 2026. 4. 17. 20:20
BriefStock 개발일지

Phase 7: 데이터 신선도 · 비용 최적화 · 유튜브 파싱 안정화

KOSPI 실시간 보정 · 전일 대비 변동률 · Claude API 비용 80% 절감 설계 · 유튜브 JSON 파싱 복구
15개 파일, +1,033줄 / -234줄 변경

프로젝트 진행 현황

Phase 6까지 완료한 상태에서, 실사용 중 발견된 데이터 정확성·비용·안정성 이슈를 집중 해결했습니다.

Phase 내용 상태
Phase 0~1 환경 세팅 + 앱 골격 완료
Phase 2~3 포트폴리오 + AI 투자 브리핑 완료
Phase 3.5~3.9 UI 리디자인 + 코드 품질 + 대시보드 레이아웃 완료
Phase 4 유튜브 채널 분석 완료
Phase 5 Supabase Auth + 배포 준비 완료
Phase 6 포트폴리오 심층 분석 + 전문가 인사이트 완료
Phase 7 데이터 신선도 + 비용 최적화 + 유튜브 안정화 이번 세션
 

01. KOSPI 지수가 하루 늦게 보이던 문제

실사용 중 "코스피가 오늘 하락 중인데 대시보드에는 상승으로 나온다"는 문제를 발견했습니다.

문제: yfinance KOSPI 데이터 1~2일 지연

yfinance의 ^KS11 history는 4/15까지만 반환되고, last_price는 4/16 종가를 보여주는 상황. 오늘(4/17) 데이터가 없었습니다.

prev_close도 4/15 종가라서 변동률 계산 자체가 틀림.

해결: pykrx KODEX 200 ETF 역산

pykrx의 인덱스 API가 깨져 있어, KODEX 200 ETF(069500)의 당일 OHLCV 변동률을 구한 뒤 yfinance KOSPI last_price에 적용하는 방식으로 해결했습니다.

services/market_data.py — KOSPI 조회 로직 1) pykrx로 KODEX200 ETF OHLCV 조회 (당일 데이터 포함) 2) ETF 전일 대비 변동률 계산 (예: -0.68%) 3) yfinance KOSPI last_price를 전일 종가로 사용 4) 전일 종가 × (1 + 변동률) = 오늘 추정 KOSPI 결과: KOSPI 6,185.95 (전일 6,226.05 대비 -0.64%) source: "KRX (pykrx+yfinance)"
시장 지표 캐시
4시간 → 10분
종목 분석 캐시
4시간 → 30분
KOSPI 지연
1~2일 → 당일
 

02. 포트폴리오 전일 대비 변동률 추가

기존에는 평균 매입가 대비 수익률만 보여줬습니다. "오늘 얼마나 올랐는지/빠졌는지"를 즉시 확인하고 싶다는 니즈.

BE: prev_close + daily_change_pct

services/price.py가 현재가뿐 아니라 전일 종가도 함께 반환하도록 확장. KR 종목은 pykrx OHLCV에서, US 종목은 yfinance fast_info.previous_close에서 가져옵니다.

routers/portfolio.py에서 (현재가 - 전일종가) / 전일종가 × 100으로 변동률 계산.

FE: 이중 수익률 표시

종목 카드 우측에 전일 대비(▲/▼)와 평단 대비(배지) 두 가지를 함께 표시합니다.

삼성전자                      ₩216,000
005930 · 50주 · 평균 ₩200,000    ▼0.69%   +8.00%
                                 전일대비   평단대비
 

03. Claude API 비용 최적화

3일간 briefings 테이블을 조사했더니, 하루에 22건이나 저장된 날이 있었습니다. 캐시가 있는데도 중복 호출이 발생.

문제: 동시 요청 시 중복 Claude 호출

사용자가 대시보드를 열면 뉴스 브리핑 + 전문가 분석이 동시 요청됩니다. 캐시 확인 → 없음 → Claude 호출이 두 요청 모두에서 실행되어 같은 분석이 2번 생성·저장.

해결 1: 사용자별 asyncio.Lock

briefing.pyasset_management.py에 사용자 ID별 Lock을 추가. 같은 사용자의 동시 요청은 첫 번째가 완료될 때까지 대기 → 두 번째는 캐시 히트.

해결 2: ANTHROPIC_FAST_MODEL 환경변수

구조화 JSON 출력(유튜브/전문가 분석)에는 Haiku로 충분합니다. .env에 한 줄 추가하면 해당 호출만 경량 모델로 전환.

# .env (API tier 업그레이드 후 활성화) ANTHROPIC_FAST_MODEL=claude-haiku-4-5-20251001 # 모델 결정 로직 뉴스 브리핑 → ANTHROPIC_MODEL (Sonnet) — 품질 중요 유튜브 분석 → ANTHROPIC_FAST_MODEL (Haiku) — JSON 구조화 전문가 인사이트 → ANTHROPIC_FAST_MODEL (Haiku) — JSON 구조화
항목 기존 최적화 후
중복 호출 하루 최대 22건 최대 3건 보장
유튜브/전문가 모델 Sonnet (고비용) Haiku 전환 가능 (80% 절감)
일일 비용 (예상) ~$0.08 + 중복 ~$0.04
 

04. 유튜브 JSON 파싱 안정화

유튜브 분석 결과가 빈 화면이거나 raw JSON이 그대로 노출되는 문제.

문제: Claude가 JSON을 중첩 반환

Claude에게 JSON 출력을 요청했더니, consensus_summary 값 안에 전체 JSON을 코드블록으로 감싸서 넣어버림. 외부 래퍼는 빈 배열만 남고, 실제 데이터는 문자열 속에 갇힌 구조.

실제 DB에 저장된 깨진 데이터: { "consensus": "neutral", ← 외부 래퍼 (빈 기본값) "consensus_summary": "```json\n{ ← 여기 안에 실제 분석이 통째로! \"consensus\": \"bullish\", \"keywords\": [...], ... }\n```", "keywords": [], ← 비어있음 "channel_summaries": [] ← 비어있음 }

해결: 3단계 파싱 파이프라인

_parse_claude_json() 함수를 신규 작성하여 다양한 형태의 Claude 응답을 모두 처리합니다.

_parse_claude_json() 파싱 파이프라인 1단계: 코드블록 제거 (```json ... ``` → 순수 JSON) 2단계: 중첩 JSON 감지 → consensus_summary가 "{"로 시작하면 내부 JSON 추출 → 내부에 필수 키(consensus, keywords 등)가 있으면 대체 3단계: 잘린 JSON regex 폴백 → 불완전한 JSON에서 consensus, consensus_summary 추출 → 완전한 keyword 객체만 regex로 복구

프롬프트 강화

근본 원인 방지를 위해 프롬프트에 명시적 제약을 추가했습니다:

• "반드시 순수 JSON만 출력. ```json 코드블록으로 감싸지 마세요"

• "consensus_summary 값은 반드시 일반 텍스트 문장이어야 합니다"

• FE에서도 방어: consensus_summary{ 또는 ```로 시작하면 렌더링 차단

 

05. 변경 파일 요약

15개 파일, +1,033줄 / -234줄

파일 변경 내용
services/market_data.py KOSPI pykrx 역산, 캐시 10분
services/price.py prev_close 함께 반환
services/stock_analysis.py 캐시 30분
routers/portfolio.py daily_change_pct 추가
services/claude_client.py model_override + get_fast_model()
services/briefing.py 사용자별 Lock
services/asset_management.py 사용자별 Lock + fast model
services/youtube.py 파싱 파이프라인 + 프롬프트 강화
backend/main.py 0.0.0.0 바인딩
frontend/src/lib/api.ts 타입 확장
frontend/.../PortfolioList.tsx 전일대비 ▲/▼ 표시
frontend/.../YouTubeSection.tsx JSON 렌더링 차단, 미사용 코드 제거
.env.example ANTHROPIC_FAST_MODEL 가이드
HANDOFF.md Phase 7 작업 기록
docs/dashboard-review-prompt.md 전문 투자자 관점 검토 프롬프트
 

06. 회고: 실사용이 드러내는 문제들

Phase 6까지는 "기능을 만드는" 단계였다면, Phase 7은 "실제로 쓰면서 고치는" 단계였습니다.

배운 점

1. 데이터 소스를 맹신하지 말 것. yfinance가 한국 시장 데이터를 1~2일 지연 반환한다는 건 코드만 봐서는 알 수 없었습니다. 사용자가 "코스피가 하락 중인데 상승으로 나온다"고 말해줘야 발견. 데이터 파이프라인에는 반드시 교차 검증이 필요합니다.

2. LLM의 JSON 출력은 신뢰할 수 없다. "JSON만 출력하세요"라고 요청해도 Claude는 코드블록으로 감싸거나, 값 안에 JSON을 중첩하거나, 출력을 중간에 자릅니다. 프롬프트 + 파싱 + FE 방어를 3중으로 해야 안정적입니다.

3. 캐시가 있어도 동시성을 고려해야 한다. 일별 캐시가 적용되어 있었지만, 대시보드 로드 시 여러 컴포넌트가 동시에 API를 호출하면서 캐시 미스 구간에서 중복 생성이 발생했습니다. Lock 한 줄로 비용이 절반 이상 줄었습니다.

다음 단계: Claude API 크레딧 충전 후 ANTHROPIC_FAST_MODEL=claude-haiku-4-5-20251001 활성화로 비용 80% 추가 절감. 그리고 docs/dashboard-review-prompt.md에 정리한 전문 투자자 관점 검토 프롬프트로 대시보드 고도화를 진행할 예정입니다.