본문 바로가기

개발 기록/미국 증시 분석 자동화 시스템 운영기

[운영기 A-3] 주간·월간·분기 파이프라인 설계 — 7일·30일·90일 데이터를 Claude에 넘기는 방법

이 시리즈는 증시 자동화 구축기 8편의 후속입니다.

이전 글: 운영기 A-2 — 이벤트 파이프라인 옆에서 돌아가는 독립 파이프라인들
다음 글: 운영기 B-1 — 실적·경제지표 캘린더 구현: 데이터가 시간차로 들어올 때


일간 블로그와 무엇이 다른가

일간 블로그는 단순하다.

하루치 데이터를 Claude에 넘기고, Claude가 마크다운 한 편을 반환하면 끝이다.

API 호출 1회.

 

주간·월간·분기는 다르다.

데이터 범위가 7일·30일·90일로 커지고, 분석해야 할 관점도 늘어난다.

한 번의 Claude 호출로 모든 것을 처리하려 하면 두 가지 문제가 생긴다.

 

첫째, 프롬프트가 너무 길어진다.

7일치 뉴스와 지수 데이터를 그대로 넣으면 토큰 한계에 걸리거나, Claude가 중요한 부분을 흘려보낸다.

 

둘째, 출력 품질이 고르지 않다.

"오늘 시황 + 종목 분석 + 포트폴리오 현황 + 다음 주 전략"을 한 번에 요청하면 어떤 섹션은 깊고 어떤 섹션은 얕게 나온다.

 

해결은 단계별 Claude 호출이다.

관점을 나눠서 각각 요청하고, 결과를 조합해서 하나의 문서를 만든다.

 


주간 블로그 

Claude 6단계 파이프라인

 

매주 일요일 09:10에 실행되며,

지난 7일의 데이터를 종합한다.

@Scheduled(cron = "0 10 9 * * SUN", zone = "Asia/Seoul")
public void generateWeeklyBlogNow() { ... }

 

6단계가 순차 실행된다.

단계 내용 Claude 호출
1단계 주간 시장 흐름 요약 1회
2단계 보유 종목별 주간 뉴스·분석 요약 종목 수만큼
3단계 주간 실적·경제지표 이슈 정리 1회
4단계 포트폴리오 변동 및 다음 주 전략 1회
5단계 블로그 제목 생성 1회
6단계 DALL-E 대표 이미지 생성 1회

 

 

각 단계는 이전 단계 결과를 컨텍스트로 포함한다.

 

1단계 시장 흐름 요약이 2단계 종목 분석의 배경이 되고, 1~3단계 결과가 4단계 전략 수립에 쓰인다.

Claude 호출 횟수가 많아지는 대신 각 단계의 집중도가 높아진다.

 

컨텍스트로 들어가는 데이터는 일간보다 훨씬 풍부하다.

 

  • 7일치 ChatGPT 분석 결과 (종목별 BUY/HOLD/SELL 판단 이력)
  • 7일치 AI 예측 적중률 통계
  • 주간 S&P500·나스닥·다우 등락 요약
  • 주간 Tier 1·2 경제지표 발표 결과 + Surprise 분류
  • 주간 실적 발표 결과 (BEAT/MISS/INLINE)
  • 포트폴리오 주간 수익률 변화

이 데이터를 단계별로 압축해서 Claude에 넘기면 토큰 한계 없이 깊이 있는 분석이 나온다.

 

[이미지 1 — 주간·월간·분기 파이프라인 달력 뷰]


주간 이슈 자동 발굴

 주간 블로그 완료 후 자동 트리거

 

 

주간 블로그가 생성되면 즉시 WeeklyIssueDiscoveryService.discoverNow()가 호출된다.

스케줄이 따로 없다. 주간 블로그 완료가 트리거다.

 

목적은 이번 주에 시장에서 중요했던 이슈를 자동으로 발굴해서,

사용자가 심층 분석 블로그를 생성할 이슈를 고를 수 있게 준비하는 것이다.

 

발굴은 2단계로 나뉜다.

 

1단계 — 정량 필터 (자동)

숫자로 판단할 수 있는 조건을 먼저 걸러낸다.

시장 충격
├─ 주간 S&P500 등락 ±2% 이상
└─ 주간 나스닥 등락 ±3% 이상
뉴스 집중도
├─ 동일 market_theme 뉴스 5건 이상
├─ signal_score 5점 이상 뉴스 2건 이상
└─ NEGATIVE 감성 비율 60% 이상
거시경제 키워드
├─ FOMC / Fed / Powell 키워드 3건 이상
└─ CPI / PPI / 고용 키워드 3건 이상

 

이 필터를 통과한 항목이 Claude 평가 대상이 된다.

 

2단계 — Claude 중요도 평가

필터를 통과한 이슈를 Claude에 넘겨 점수화한다.

 

평가 기준:
포트폴리오 영향도 (1~5점)

 

시장 파급력 (1~5점)
다음 주 지속 여부 (+2점 보너스)
참신성 체크 (기존 이슈 중복 제거)


선정: 합산 상위 최대 5개 / 3개 미만이면 발굴 중단

 

 

선정된 이슈는 DB에 저장되고, Telegram으로 "N건 발굴됨" 알림이 온다.

이후 사용자가 프론트엔드 이슈 목록에서 원하는 이슈의 [블로그 생성] 버튼을 누르면 Claude 심층 분석 블로그가 생성된다.

 

자동 발굴이지만 선택은 사람이 하도록 구현했다.

완전 자동화보다 이 방식이 블로그 품질 관리에 더 적합하다고 판단했다.


월간 블로그 

Claude 9단계 파이프라인

 

 

매달 말일 11:00에 실행된다.

@Scheduled(cron = "0 0 11 L * *", zone = "Asia/Seoul")
public void generateMonthlyBlogNow() { ... }

 

L은 cron에서 "해당 월의 마지막 날"을 의미한다.

별도로 말일 계산을 구현할 필요가 없다.

 

9단계 파이프라인이 순차 실행된다.

 

단계 내용
1단계 이달 시장 흐름 요약
2단계 이달 매매 기록 분석
3단계 수익 현황 (종목별 손익, 전월 대비)
4단계 AI 예측 월간 적중률 분석
5단계 이달 실수와 개선 포인트
6단계 다음 달 전략
7단계 블로그 제목 생성
8단계 전체 내용 DB 저장
9단계 DALL-E 대표 이미지 생성

 

주간(6단계)보다 3단계가 더 많다.

추가된 것은 "실수와 반성", "다음 달 전략"처럼 회고 관점의 섹션이다.

한 달치 데이터를 요약하다 보면 자연스럽게 더 많은 관점이 필요해진다.

 

이미지 생성(9단계)이 실패해도 텍스트 초안은 DB에 저장된 상태를 유지한다.

9단계 실패가 전체를 롤백하지 않는다.

 

[이미지 2 — Claude 단계별 호출 횟수 비교]


분기 AI 비중 제안

90일 데이터와 JSON 파싱 오류

 

 

매 분기 말일(3/6/9/12월 마지막 날) 11:00에 실행된다.

@Scheduled(cron = "0 0 11 L 3,6,9,12 *", zone = "Asia/Seoul")

cron에서 L 3,6,9,12는 "3월, 6월, 9월, 12월의 마지막 날"을 의미한다.

컨텍스트 구성

Claude에 넘기는 데이터가 가장 방대하다.

 

90일 뉴스 sentiment 집계 (종목별 POS/NEG/NEU 비율)
최근 실적 발표 결과 (BEAT/MISS/INLINE)
30일 Tier 1·2 경제지표 발표 결과
현재 종목별 목표 비중
섹터 집중도 규칙 (40% 초과 억제)

 

Claude에 요청하는 것은 간단하다.

현재 포트폴리오의 종목별 비중을 얼마나 늘리거나 줄여야 하는지 delta(±%)로 알려달라는 것이다.

 

// Claude 응답 예시 (기대 형식)
{
  "NVDA": { "delta": 3.0, "reason": "AI 수요 지속..." },
  "TSLA": { "delta": -2.0, "reason": "거시 압력..." },
  "AAPL": { "delta": 0.0, "reason": "중립 유지..." }
}

조건이 두 가지 붙는다. 모든 delta의 합계는 0이어야 한다(비중 재조정이므로).

특정 섹터 집중도가 40%를 넘으면 해당 섹터 종목의 delta를 줄이도록 프롬프트에 명시했다.

 

운영에서 만난 파싱 오류 (ISSUE-011)

 

처음 배포했을 때 파이프라인이 500 오류로 즉시 죽었다.

 

Unexpected character ('+') in numeric value: JSON spec does not allow numbers to have plus signs

 

Claude가 양수 delta를 3.0이 아닌 +3.0으로 반환했다.

 

JSON 표준(RFC 8259)은 숫자 앞에 + 기호를 허용하지 않는다.

Jackson의 기본 파서도 마찬가지다.

 

해결은 두 단계였다.

// 1. JSON 추출 직후 + 기호 제거
String json = extractJson(rawResponse);
json = json.replaceAll(":\\s*\\+([0-9])", ": $1");

// 2. 프롬프트 예시 수정
// 변경 전: "(예: +3.0, -2.0, 0.0)"
// 변경 후: "(예: 3.0, -2.0, 0.0) — 양수는 부호 없이, 음수만 - 기호 사용"

정규식으로 파싱 직전에 정제하고, 프롬프트 자체도 수정해서 근본 원인을 차단했다.

 

4편에서 다룬 AI 응답 파싱 정제 패턴과 동일한 원칙이다.

 


주간 이슈 자동 발굴 흐름

주간 블로그 생성 완료

WeeklyIssueDiscoveryService.discoverNow() 자동 호출

1단계: 정량 필터 (DB 조회만, Claude 미사용)

시장 충격 조건 확인
뉴스 집중도 조건 확인
거시경제 키워드 조건 확인

2단계: Claude 중요도 평가 (필터 통과 항목만)
포트폴리오 영향도 + 시장 파급력 + 지속성 + 참신성
합산 상위 5개 선정 (3개 미만이면 중단)

Telegram: "이번 주 이슈 N건 발굴됨"

사용자가 프론트엔드에서 [블로그 생성] 버튼 클릭

WeeklyIssueBlogService: Claude 심층 분석 블로그 생성

 

 

정량 필터를 먼저 거르는 이유는 비용 때문이다.

Claude 평가는 필터를 통과한 항목만 받는다. 아무 이슈도 없는 평온한 주에는 Claude 호출 자체가 발생하지 않는다.

 

[이미지 3 — 주간 이슈 발굴 플로우]


왜 단계별 호출인가

월간 블로그를 처음 만들 때 한 번의 거대한 프롬프트로 모든 것을 요청했다.

 

결과가 두 가지 문제를 냈다.

 

첫째, 어떤 섹션은 상세하고 어떤 섹션은 한두 문장으로 끝났다.

Claude가 긴 프롬프트에서 중요도를 스스로 판단해버렸다.

 

둘째, 재생성이 어려웠다.

매매 기록 섹션만 다시 쓰고 싶어도 전체 프롬프트를 다시 실행해야 했다.

 

단계별로 나누면 두 문제가 모두 해결된다.

각 단계는 그 섹션만 집중해서 쓴다.

특정 단계만 실패하면 그 단계만 재실행하면 된다.

 

실행 시간이 길어지는 단점이 있지만,

월간 블로그는 한 달에 한 번이라 허용 가능한 비용이다.


정리

파이프라인 실행 주기 Claude 단계 데이터 범위 특이사항
주간 블로그 매주 일요일 6단계 7일 완료 후 이슈 발굴 자동 트리거
주간 이슈 발굴 주간 블로그 직후 정량 필터 + 1회 7일 3건 미만이면 중단
월간 블로그 매월 말일 9단계 30일 이미지 실패해도 텍스트 유지
AI 비중 제안 분기 말일 1회 + 파싱 90일 + 기호 JSON 파싱 오류 처리 필요

 

그룹 A 편(A-1, A-2, A-3)에서 파이프라인 진화 과정을 모두 다뤘다.

다음 그룹 B에서는 파이프라인이 다루는 도메인들을 살펴본다.

 

실적·경제지표 데이터를 어떻게 설계했는지,

시간차 데이터가 들어올 때 DB 스키마를 어떻게 잡았는지부터 시작한다.