06. LLM 멀티 에이전트로 종목 분석 자동화
LLM 멀티 에이전트로 종목 분석 자동화
들어가며
앞 편에서 매매 체크리스트 8개를 만들어놓고, 나는 두 항목 앞에서 막혔다. 시총·PEG·이동평균선·비중은 코드로 계산하면 끝인데, 이 둘은 계산으로 풀리지 않았다.
- 재무제표 해석 — "이 회사 영업현금흐름이 이익과 안 맞는데, 위험한가 아니면 일시적인가?"
- 뉴스 판단 — "자사주 매입 발표는 호재인데, 이게 구조적 호재인가 일시적 노이즈인가?"
이건 읽고, 이해하고, 맥락을 판단하는 일이었다. 코인 봇을 만들 땐 이런 걸 자동화할 방법이 없어서 포기했던 영역이다. 그런데 LLM이 등장하면서 길이 열렸다. 사람 애널리스트가 하던 판단을, 코드가 호출할 수 있게 된 것이다.
이 글에서는 내가 그걸 어떻게 구현했는지 — 투자 회사의 팀 구조를 그대로 코드로 복제한 멀티 에이전트 시스템을 해부한다. 만들면서 가장 재밌었던 부분이다.
1. 왜 "하나의 거대한 프롬프트"가 아니라 "여러 에이전트"인가
처음엔 나도 단순하게 생각했다. "재무제표랑 뉴스랑 차트 데이터를 다 LLM에 한 번에 넣고 '이 종목 살까 말까' 물어보면 되잖아?" 실제로 그렇게 만들어봤다.
작동은 했다. 하지만 품질이 나빴다. 이유는 사람 조직과 똑같았다.
| 구분 | 한 명에게 다 시키기 | 팀으로 나누기 |
|---|---|---|
| 방식 | "재무도 보고 뉴스도 보고 차트도 보고 결론도 내" | 재무 전문가 → 재무만 깊게 / 뉴스 전문가 → 뉴스만 깊게 / 기술 전문가 → 차트만 깊게 |
| 결과 | 주의가 분산돼 다 얕아짐 | 각자 깊게 본 뒤 종합 |
| 오류 | 한 영역의 실수가 전체 오염 | 한 영역 실수가 격리됨 |
실제 투자 회사가 애널리스트를 분야별로 두고, 마지막에 CIO(최고투자책임자)가 종합 판단을 내리는 데는 이유가 있었다. 전문화는 품질을 높이고, 분리는 오류를 격리한다. 그래서 나는 시스템도 그 조직도를 그대로 본떴다. 말하자면 내 노트북 안에 작은 투자 회사를 차린 셈이다.
2. 에이전트 조직도
- Agent 0: 스크리너 (코드) — 수백 종목 → 분석 대상 수십 개로 압축
- → (fan-out: 병렬 분석) 세 전문가에게 분배
- 병렬 분석 — 재무 애널리스트 / 뉴스 애널리스트 / 기술적 분석가가 동시에 분석
- → (fan-in: 종합) 결과를 CIO로 취합
- CIO (총괄 에이전트) — 최종 판단 + 리포트
핵심 흐름은 스크리너로 좁히고 → 전문가들이 병렬로 분석하고 → CIO가 종합하는 3단이다. 하나씩 뜯어보자.
3. Agent 0 — 스크리너 (LLM을 쓰지 않는 에이전트)
가장 먼저, 그리고 내가 비용 때문에 가장 신경 쓴 장치다.
- 역할 — 종목 후보 1차 필터링
- 입력 — 전체 종목 리스트 + 시세 데이터
- 처리 — 시총 필터 (KR 10조 / US $10B) + PER/PEG/ROE 컷오프
- 출력 — 분석 대상 수십 개 종목
- 모델 — 없음 — 순수 규칙 기반 코드
- 비용 — $0 (LLM 토큰 소비 없음)
왜 이게 중요했냐면 — 전체 수백 종목에 비싼 LLM을 다 돌리면 비용이 폭발했기 때문이다. 처음 무심코 전체를 돌렸다가 청구서를 보고 식겁했다. 스크리너가 코드만으로 후보를 수십 개로 줄여놓으면, 그 다음 단계의 LLM 비용이 수십 분의 일로 줄었다.
규칙으로 풀 수 있는 건 끝까지 코드로 풀고, LLM은 정말 "판단"이 필요한 곳에만 쓴다. 이게 내가 LLM 파이프라인 비용 설계에서 제1원칙으로 삼은 것이다.
4. Agent 1 — 재무 애널리스트
이제부터 LLM이다. 첫 번째 전문가는 재무제표를 읽는다. 나는 이 에이전트에게 가상의 경력까지 부여했다.
페르소나: "당신은 CFA 자격을 보유한 10년차 재무 애널리스트입니다. 특히 손익계산서와 현금흐름표의 괴리를 감지하는 데 전문성이 있습니다."
- 입력 — DART(한국) / SEC EDGAR(미국)에서 수집한 재무제표 JSON
- 모델 — Sonnet 급 (복잡한 재무 해석)
5편에서 강조한 손익 ↔ 현금흐름 크로스체크를, 이 에이전트가 사람 CFA처럼 수행한다. 그리고 결과를 구조화된 JSON으로 뱉게 했다.
{
"ticker": "005930",
"grade": "A", // A~F 등급
"profitability": 85, // 수익성 0~100
"stability": 78, // 안정성
"growth": 72, // 성장성
"cashflow_quality": 90, // 손익-현금흐름 일치도
"red_flags": [], // 위험 신호 목록
"summary": "매출 3년 연속 성장, 영업CF 양호, 부채비율 안정적..."
}
자연어 보고서가 아니라 JSON으로 받는 게 핵심이었다. 그래야 뒤에서 코드가 점수를 비교하고 정렬할 수 있으니까. (LLM이 JSON 형식을 어기지 않도록 강제하는 기법은 뒤에서 다룬다. 이것 때문에 한참 고생했다.)
5. Agent 2 — 뉴스 애널리스트
두 번째 전문가는 뉴스와 공시를 읽고, 호재·악재를 판별한다.
페르소나: "당신은 금융 시장 전문 저널리스트 출신의 센티멘트 분석가입니다. 단순 노이즈와 실질적 이벤트를 구분하는 능력이 핵심입니다."
- 입력 — 최근 30일 뉴스/공시 (한국: 네이버 / 미국: Finnhub 영문)
- 모델 — Sonnet 급 (다국어 + 분류)
여기서 LLM의 진가에 나는 좀 감탄했다. 단순히 "긍정/부정"을 세는 게 아니라, 일시적 노이즈와 구조적 이벤트를 구분한다. "주가 5% 하락"이라는 기사는 결과일 뿐이지만, "핵심 고객사 이탈"은 구조적 악재다. 이 둘을 가려내는 게 예전엔 사람 애널리스트만의 일이었는데, 이제 에이전트가 한다.
{
"ticker": "005930",
"sentiment_score": 65, // -100 ~ +100
"positive_events": ["자사주 매입 발표", "AI 반도체 수주 확대"],
"negative_events": [],
"impact_level": "medium", // 영향도
"news_volume": "normal", // 뉴스 양
"summary": "자사주 매입 호재, AI 사업 확장 긍정적..."
}
6. Agent 3 — 기술적 분석가
세 번째 전문가는 차트를 본다. 단, 여기엔 내가 일부러 넣은 영리한 분업이 있다.
- 입력 — 최근 120일 시세 (OHLCV)
- 모델 — Haiku 급 (가장 저렴)
- ⚠ 핵심 — SMA 계산은 LLM이 하지 않는다. 코드가 정확히 계산하고, LLM은 '해석'만 한다.
SMA 5/20/60 값을 LLM에게 계산시키는 건 멍청한 짓이다. 산수는 코드가 100% 정확하게 한다. LLM은 그 결과를 받아 "정배열 직전이다", "박스권이라 신호 신뢰도가 낮다" 같은 맥락적 해석만 담당한다. 그래서 가장 싼 모델(Haiku)로 충분했다.
{
"ticker": "005930",
"arrangement": "golden", // 정배열/역배열/혼조
"box_range": false, // 박스권 여부
"sma5": 72500, "sma20": 71200, "sma60": 68900,
"volume_trend": "increasing",
"signal": "buy_ready", // buy_ready/hold/sell_warning
"summary": "정배열 진입 직전, SMA5가 SMA20 상향 돌파 임박..."
}
일을 LLM과 코드로 나누는 이 감각이 시스템 전체의 품질과 비용을 좌우했다. 계산은 코드, 판단은 LLM. 산수를 LLM에 맡기면 비싸고 부정확하고, 판단을 코드에 맡기면 경직된다. 이 경계를 어디에 긋느냐가 엔지니어의 실력이라고 생각한다.
7. Agent 4 — CIO (최종 종합)
세 전문가의 JSON 리포트가 모이면, 마지막으로 CIO 에이전트가 종합 판단을 내린다. 내가 가장 공들인 프롬프트다.
페르소나: "당신은 헤지펀드의 CIO입니다. 세 애널리스트의 리포트를 종합하여 최종 투자 의견을 결정합니다. 리스크 관리를 최우선으로 하며, 확신이 없으면 'PASS'합니다."
- 입력 — Agent 1 + 2 + 3의 JSON 전부 + 현재 포트폴리오 상태
- 모델 — Opus 급 (최고 품질 — 종합 판단)
{
"ticker": "005930", "name": "삼성전자", "market": "KR",
"final_grade": "A",
"action": "BUY_READY", // BUY_READY/WATCH/HOLD/SELL/PASS
"confidence": 82,
"scores": { "fundamental": 85, "sentiment": 65, "technical": 78 },
"position_size": "8%", // 추천 비중 (10% 상한 내)
"entry_condition": "SMA5가 SMA20 상향 돌파 시 진입",
"stop_loss": "SMA5 < SMA60 이탈 시 매도",
"report": "재무 건전성 우수(A), 센티멘트 양호, 정배열 임박..."
}
내가 이 프롬프트에 가장 힘주어 박은 지시는 "확신이 없으면 PASS한다" 였다. 시스템의 목적은 "최대한 많은 매수 신호를 만드는 것"이 아니라 "확실한 것만 거르는 것"이니까. 신호를 남발할수록 손실만 쌓인다는 걸 겪고 나서, 나는 이 한 줄을 새겼다. 1편의 철학 — 투자는 도박이 아니라 규율 — 이 CIO 프롬프트에 그대로 박혀 있다.
8. 모델 차등 배치 — 비용 설계의 핵심
여기가 엔지니어링의 묘미였다. 모든 에이전트에 최고 모델을 쓰면 파산한다. 나는 분석 난이도에 따라 모델을 차등 배치했다.
| 에이전트 | 모델 | 이유 | 단가/종목 |
|---|---|---|---|
| 스크리너 | 코드 | 규칙 기반, LLM 불필요 | $0 |
| 기술적 분석가 | Haiku | 계산은 코드, 해석만 | ~$0.01 |
| 뉴스 애널리스트 | Sonnet | 다국어 분류 + 영향도 판단 | ~$0.08 |
| 재무 애널리스트 | Sonnet | 복잡한 재무 크로스체크 | ~$0.08 |
| CIO 총괄 | Opus | 종합 판단, 최고 품질 필요 | ~$0.25 |
싼 모델로 충분한 일에 비싼 모델을 쓰는 건 낭비고, 중요한 종합 판단에 싼 모델을 쓰는 건 위험하다. 일의 난이도와 모델의 급을 맞추는 것 — 이게 LLM 시스템 비용 설계의 본질이라는 걸, 청구서를 줄여가며 체득했다.
9. 프롬프트 캐싱 — 비용을 1/5로
또 하나, 알고 나서 감탄한 기법. 각 에이전트의 시스템 프롬프트(페르소나 + 분석 루브릭)는 모든 종목에 대해 동일하다. 수십 종목을 연달아 분석하면 같은 프롬프트를 수십 번 다시 보내는 셈이었다.
- 캐싱 없이
- → 종목1: [긴 시스템 프롬프트 2000토큰] + [종목1 데이터] ← 풀 과금
- → 종목2: [긴 시스템 프롬프트 2000토큰] + [종목2 데이터] ← 풀 과금
- → ...50번 반복... → 시스템 프롬프트 값을 50번 냄
- 캐싱 적용 (cache_control)
- → 종목1: [시스템 프롬프트 — 캐시 기록] + [종목1 데이터]
- → 종목2: [시스템 프롬프트 — 캐시 히트 90%↓] + [종목2 데이터]
- → 2번째부터 시스템 프롬프트 비용 거의 0
시스템 프롬프트에 cache_control: { type: 'ephemeral' }를 지정하고 5분 안에 종목들을 순차 처리하면, 두 번째 종목부터 캐시 히트로 해당 부분 비용이 약 90% 줄어든다. 전체 비용이 대략 1/5로 떨어졌다. 이 한 줄 설정으로 청구서가 눈에 띄게 얇아지는 걸 보고 기뻤다. (Anthropic API의 프롬프트 캐싱 기능이다.)
10. JSON을 어기지 않게 만들기 + 장애 격리
LLM 시스템을 실전에 올릴 때, 설계도엔 없던 두 가지 현실 문제가 나를 괴롭혔다.
① JSON 출력 보장 — LLM은 가끔 형식을 어긴다. 멀쩡히 잘 되다가 한 종목에서 깨진 JSON을 뱉으면 파이프라인이 멈췄다. 이걸 막으려고 Claude의 Tool use를 썼다. 출력 스키마를 도구로 정의하고 그 도구만 쓰도록 강제(tool_choice)하면, 응답이 항상 유효한 JSON으로 나온다. 자연어로 받아 정규식으로 파싱하던 초기 방식보다 비교할 수 없이 견고해졌다.
② 장애 격리 — 50개 종목을 분석하다 3번 종목에서 에러가 나도, 나머지 47개는 계속 가야 했다. Promise.allSettled 패턴으로 개별 종목 실패가 전체 배치를 멈추지 않게 했다. 그리고 429(요청 과다)·529(과부하) 응답에는 지수 백오프로 3회 재시도한다. 코인 봇에서 API가 죽을 때마다 전체가 멈춰 밤새 복구하던 기억이, 이 설계를 처음부터 박게 만들었다.
- 종목1 ✅ → 결과 저장
- 종목2 ✅ → 결과 저장
- 종목3 ❌ (에러) → 로그만 남기고 다음으로 진행
- 종목4 ✅ → 결과 저장
- → 한 종목의 실패가 47개의 성공을 막지 않는다
정리
- 하나의 거대 프롬프트보다 전문화된 멀티 에이전트가 품질↑
- 조직도: 스크리너(코드) → 재무·뉴스·기술(병렬) → CIO(종합)
- 계산은 코드, 판단은 LLM (특히 SMA는 코드가 계산)
- 모델 차등 배치: Haiku/Sonnet/Opus를 난이도에 맞춰
- 프롬프트 캐싱으로 비용 약 1/5
- Tool use로 JSON 강제, allSettled로 장애 격리
- CIO의 원칙: 확신 없으면 PASS (규율 > 신호 남발)
이제 부품은 다 모였다. 스크리너, 네 종류의 에이전트, 비용 최적화, 장애 처리. 마지막 편에서는 이 부품들을 실제로 매일 자동으로 돌아가는 하나의 파이프라인으로 조립한 이야기를 한다. 종목 40개로 실험하던 시스템을 702개로 늘렸을 때 깨진 것들, 그리고 모든 자동화의 끝에서 내가 왜 여전히 사람의 손을 남겨뒀는지.