이 시리즈는 증시 자동화 구축기 8편의 후속입니다.
구축기를 먼저 읽지 않아도 이해할 수 있지만, 함께 읽으면 흐름이 더 자연스럽습니다.
이전 글: 운영기 A-1 — Spring Boot 파이프라인을 이벤트 기반으로 바꾼 이유
다음 글: 운영기 A-3 — 주간·월간·분기 파이프라인: Claude가 긴 글을 쓰는 방법
파이프라인이 하나가 아니다
A-1편에서 이벤트 기반 3그룹 파이프라인으로 전환한 과정을 다뤘다.
그런데 이 파이프라인은 시스템 전체의 절반도 되지 않는다.
매일 06:00부터 09:40 사이에 이벤트 파이프라인과 별개로 돌아가는 파이프라인이 4개 더 있다.
- 실적 캘린더 수집·업데이트·블로그 생성
- 경제지표 수집·Surprise 분석·블로그 생성
- ETF 배당 수집·GPT 분석·Claude 블로그 생성
- 종가 수집, Notion 동기화
각각 완전히 다른 주기와 트리거를 가진다. 서로를 모르고, 이벤트 파이프라인도 모른다. 그냥 각자의 시간에 각자 실행된다.
이 편은 그 독립 파이프라인들이 어떻게 설계됐는지 기록한다.

실적 파이프라인 — 시간차 데이터를 다루는 방법
실적 데이터는 단순 수집이 아니다. 예정값이 먼저 들어오고, 나중에 실제값이 채워진다.
이 흐름을 처리하는 스케줄이 3개 분리되어 있다.
1. 월요일 06:10 — 향후 14일 실적 일정 수집
@Scheduled(cron = "0 10 6 * * MON")
public void collectEarningsCalendar() {
// Finnhub /api/v1/calendar/earnings?from=today&to=today+14
// tb_earnings_calendar INSERT ON DUPLICATE KEY UPDATE
}매주 월요일 한 번만 수집한다.
14일 치 일정을 한 번에 가져오고, 중복 수집 시 안전하게 덮어쓴다
(UNIQUE KEY (ticker, report_date) upsert).
매일 수집할 이유가 없다.
2. 매일 07:50 — 실적 결과 업데이트 + 블로그 자동 생성
전날~당일 실적 결과(EPS actual)를 Finnhub에서 가져와 업데이트한다.
신규 결과가 있으면 EPS 차이를 계산해서 BEAT / MISS / INLINE을 판정한다.
| BEAT | `(actual - estimate) / |
| MISS | `(actual - estimate) / |
| INLINE | 그 외 |
판정이 끝나면 blog_triggered = 0인 신규 결과에 대해 실적 심층 분석 블로그를 자동 생성한다.
Claude 3단계 파이프라인(원인 분석 → 시장 영향 → 향후 전망) + DALL-E 이미지가 순차로 실행된다.
3. 매일 08:00 — D-7 실적 임박 알람
7일 이내 실적 발표 예정 종목을 Telegram으로 보낸다.
단순 조회와 알림뿐이라 실패해도 파이프라인에 영향이 없다.
경제지표 파이프라인 — Surprise를 계산하는 구조
경제지표도 실적과 구조가 비슷하다.
예정값이 먼저, 실제값이 나중이다.
1. 매일 06:00 — 당일 경제지표 일정 수집
Finnhub Economic Calendar API로 당일 발표 예정 경제지표를 수집한다.
여기서 중요한 처리가 두 가지 있다.
첫째, UTC → KST 변환이다.
Finnhub의 time 필드는 UTC 기준이라 그대로 쓰면 한국 시간과 맞지 않는다.
둘째, Tier 분류다.
지표 이름에 키워드 매칭으로 중요도를 나눈다.
Tier 1: CPI, Core CPI, PCE, NFP, FOMC, Fed Funds Rate 등
Tier 2: PMI, ISM, GDP, 소매판매, 실업률 등
Tier 3: 그 외 저중요도 지표
이 Tier 정보가 이벤트 파이프라인의 ChatGPT 분석과 BlogService 프롬프트에 자동으로 포함된다.
경제지표 수집(06:00)이 이벤트 파이프라인 시작(06:20)보다 먼저 완료되기 때문에 가능한 의존 구조다.
2. 매일 09:00 — 실제값 업데이트 + Surprise 계산 + 블로그 생성
발표된 실제값을 다시 수집해서 업데이트하고 Surprise를 계산한다.
Surprise = actual - forecast
> +0.1 → HOT (예상보다 강한 지표)
< -0.1 → COOL (예상보다 약한 지표)
그 외 → INLINETier 1 + HOT/COOL 조합이면 독립 블로그를 자동 생성한다.
Claude 3단계 파이프라인이 실행되고 Telegram으로 완료 알림이 온다.
재작성 권고 알림 — 타이밍 문제와 해결
이벤트 파이프라인은 06:20에 시작해서 블로그를 대략 07:20~07:50 사이에 완성한다.
문제는 그 이후에 중요한 정보가 들어온다는 것이다.
~07:50 일간 블로그 완성
07:50 실적 결과 발표 (NVDA BEAT, TSLA MISS)
09:00 CPI 실제값 발표 (예상치 상회 → HOT)증시 분석은 빠르게 제공하는 게, 정보를 찾는 사람에게 도움이 된다고 생각해
실적이 반영되지 않고 먼저 작성된다.
자동으로 다시 쓰는 것은 구현하지 않았다.
블로그 작성 후 내용이 바뀐다면 인지하지 못할 수 있어
인지 후 직접 수정할 수 있도록 텔레그램을 통해 알람을 보낸다
📊 실적 발표 업데이트
NVDA: BEAT (+12.3%)
TSLA: MISS (-4.1%)
→ 일간 블로그 수동 재작성 권고
POST /api/blog/write-draft/{date}완전 자동화보다 "알림을 받고 판단해서 수동으로 재작성"이 현실적으로 더 적합하다고 판단했다.
실적이나 경제지표가 블로그 방향 자체를 바꿀 수 있기 때문이다.

## ETF 배당 파이프라인 — 가장 정교한 독립 파이프라인
ETF 배당은 매일 06:45에 실행된다. 신규 배당 발표가 없으면 수집만 하고 끝난다. 신규가 감지되면 GPT 분석 → Claude 블로그 → DALL-E 이미지 → Notion 동기화까지 전체 파이프라인이 자동으로 이어진다.
**수집 소스 전략 — AUTO 모드**
각 ETF마다 수집 소스를 설정할 수 있다(`FINNHUB` / `YAHOO` / `AUTO`). AUTO 모드는 Finnhub를 먼저 시도하고 실패하면 Yahoo Finance로 폴백한다.
```java
if ("FINNHUB".equals(source)) {
return collectFromFinnhub(ticker);
} else if ("YAHOO".equals(source)) {
return collectFromYahoo(ticker);
} else { // AUTO
try {
return collectFromFinnhub(ticker);
} catch (Exception e) {
log.warn("Finnhub 실패, Yahoo 폴백: {}", ticker);
return collectFromYahoo(ticker);
}
}
중복 방지 이중 차단
같은 배당락일을 두 번 처리하지 않도록 두 단계로 막는다.
uk_ticker_ex_dateUNIQUE KEY + INSERT IGNORE → 동일 배당락일 DB 중복 차단blog_triggered플래그 → 블로그 중복 생성 차단
신규 ETF 추가가 코드 변경 없이 가능한 이유
tb_etf 테이블에 행을 INSERT하는 것만으로 다음 수집 사이클부터 자동으로 모니터링된다.
서비스 코드를 건드릴 필요가 없다.
ETF 유형(COVERED_CALL / REIT / DIVIDEND_GROWTH)에 따라 GPT 프롬프트 전략도 자동으로 분기된다.
독립 파이프라인의 공통 설계 원칙
이 파이프라인들을 설계하면서 일관되게 지킨 규칙이 세 가지 있다.
단계별 실패는 다음 단계를 막지 않는다.
ETF 배당 파이프라인에서 Notion 동기화가 실패해도 블로그 초안은 DB에 남는다.
DALL-E 이미지 생성이 실패해도 텍스트 초안은 유지된다.
전체를 롤백하는 것보다 "할 수 있는 것까지는 완료"하는 편이 운영에서 더 유용하다.
중복 실행을 DB 레벨에서 막는다
blog_triggered 플래그, UNIQUE KEY upsert, INSERT IGNORE.
스케줄러가 같은 날 두 번 실행되거나 수동으로 재실행해도 이미 처리된 건은 건너뛴다.
Telegram으로 단계별 진행 상황을 알린다
ETF 배당 파이프라인은 총 4단계 알림을 보낸다.
중간에 멈추면 어느 단계까지 성공했는지 Telegram으로 바로 확인할 수 있다.
조용히 실패하는 것보다 시끄럽게 실패하는 편이 운영하기 훨씬 쉽다.
이벤트 파이프라인과 독립 파이프라인이 충돌하지 않는 이유
두 파이프라인이 같은 시간대에 실행되는 경우가 있다.
예를 들어 경제지표 실제값 업데이트(09:00)와 이벤트 파이프라인 그룹 3 블로그 생성이 겹칠 수 있다.
충돌이 없는 이유는 단순하다.
접근하는 DB 테이블이 다르다.
경제지표 업데이트는 tb_economic_event를, 블로그 생성은 tb_blog_draft를 쓴다.
같은 테이블에 동시에 쓸 일이 없다.
예외는 하나다.
PortfolioSnapshotService(토요일 06:45)가 ExchangeRateService(토요일 06:30)와
같은 시간대에 실행되던 문제가 있었다.
스냅샷은 환율 수집 완료 후 실행되어야 정확한 원화 환산이 가능한데 순서가 보장되지 않았다.
06:30 → 06:45로 시간을 조정해서 해결했다. (ISSUE-004)
정리
| 이벤트 (핵심) | 매일 06:20 | ApplicationEvent 연쇄 | 그룹 단위 중단 |
| 실적 수집 | 매주 월요일 | @Scheduled | log.error |
| 실적 결과·블로그 | 매일 07:50 | @Scheduled | 종목별 try-catch |
| D-7 알람 | 매일 08:00 | @Scheduled | 무시 (비필수) |
| 경제지표 수집 | 매일 06:00 | @Scheduled | Telegram 오류 |
| 경제지표 실제값·블로그 | 매일 09:00 | @Scheduled | 이벤트별 try-catch |
| ETF 배당 전체 | 매일 06:45 | @Scheduled | ETF별 try-catch |
| Notion 동기화 | 매일 09:40 | @Scheduled | log.error |
다음 편에서는 이 시스템에서 가장 주기가 긴 파이프라인들을 다룬다.
일요일 주간 블로그, 월말 월간 회고, 분기말 AI 비중 제안 — 하루짜리 데이터가 아닌
7일·30일·90일 데이터를 Claude에 넘기는 설계를 기록한다.
'개발 기록 > 미국 증시 분석 자동화 시스템 운영기' 카테고리의 다른 글
| [운영기 A-1] Spring Boot 파이프라인을 시간 기반에서 이벤트 기반으로 바꾼 이유 — PipelineOrchestrator 설계 (0) | 2026.05.18 |
|---|