Files
US2KR/PLAN.md
T
2026-08-31 14:33:53 +09:00

211 lines
14 KiB
Markdown

# US2KR — 미국 장 마감 → 한국 장 시작 전 브리핑 봇 (설계 계획)
작성일: 2026-08-25
## 1. 목표
미국 정규장이 끝난 뒤(16:00 ET), 한국 장 시작(09:00 KST) 전에 **하루 1회** 미국 시장 요약 브리핑을
자동 생성·발송하는 Python 상주 프로그램.
- 입력: 지수/선물/금리/환율/원자재, 한국 연관 종목(반도체·ADR), 뉴스, 실적·경제지표 일정
- 처리: 수집 → 정규화/저장 → MiniMax(M3)로 요약(한국 시장 관점 포함) → Markdown 렌더링
- 출력: Telegram(기본) / Slack / 이메일 + 로컬 아카이브
- 운영: 24시간 상주, 휴장일·서머타임 자동 처리, 실패 시 알림
## 2. 시간 설계 (핵심)
| 항목 | ET | KST |
|---|---|---|
| 미국 정규장 마감 | 16:00 | **05:00 (EDT, 3~11월) / 06:00 (EST, 11~3월)** |
| 애프터마켓 실적 발표 집중 | 16:00~17:30 | 05:00~06:30 |
| 애프터마켓 종료 | 20:00 | 09:00 / 10:00 |
| 한국 동시호가 / 개장 | — | 08:30 / 09:00 |
**실행 시각 규칙 (하드코딩 금지, 거래소 캘린더 기반 계산)**
- 메인 브리핑: `XNYS 세션 종가 시각 + 75분` → 대략 **06:15 (EDT) / 07:15 (EST) KST**
- 애프터마켓 실적 반응까지 포함, 한국 개장 전 1.5시간 이상 여유
- 조기 폐장일(예: 추수감사절 다음날 13:00 ET)도 캘린더의 실제 close 시각을 따르므로 자동 대응
- (선택) 개장 직전 업데이트: **08:20 KST** — 야간 선물·환율·주요 헤드라인 5줄 요약
- 미국 휴장 → 해당일 스킵. 한국 휴장 → 리포트는 만들되 헤더에 "오늘 한국 휴장" 표기 (설정으로 스킵 가능)
- 시간대는 반드시 `zoneinfo`(`America/New_York`, `Asia/Seoul`)로 처리. 서머타임 전환 자동.
- 라이브러리: `exchange_calendars` (XNYS, XKRX 모두 지원 → 휴장일·조기폐장·세션 시각)
## 3. 데이터 소스
원칙: **무료·키 불필요를 1차**, 장애 대비 2차 소스, 일부 실패해도 리포트는 발행(누락 항목 표기).
### 3.1 시세 (yfinance 1차, Stooq/Finnhub 2차)
- 지수: `^GSPC ^DJI ^IXIC ^RUT ^VIX ^SOX`
- 선물(야간 흐름): `ES=F NQ=F YM=F`
- 금리: `^IRX ^FVX ^TNX ^TYX` (2y는 ^FVX 대체 또는 FRED)
- 환율/달러: `KRW=X DX-Y.NYB JPY=X`
- 원자재/크립토: `CL=F GC=F HG=F BTC-USD`
- 섹터 ETF: `XLK XLF XLE XLV XLI SMH`
- **한국 연관**: `EWY`(MSCI Korea ETF), ADR `CPNG KB SHG PKX KEP LPL SKM`,
반도체 `NVDA TSM MU AMD AVGO ASML`, 빅테크 `AAPL MSFT GOOGL AMZN META TSLA`
- 수집 항목: 종가, 전일대비 %, 장중 고저, 애프터마켓 가격(가능한 경우), 거래량 비율
### 3.2 뉴스 (RSS, `feedparser`)
- CNBC Top News / Markets, MarketWatch, Yahoo Finance, Investing.com, Fed 보도자료 RSS
- 최근 18시간 이내 기사만, 제목+요약+링크, 중복 제거(제목 유사도)
- 상위 N개(예: 40개)만 LLM에 전달 → 토큰 통제
### 3.3 일정
- 당일 발표된 주요 경제지표(CPI, 고용, FOMC 등)와 다음 24시간 예정 이벤트
- 1차: FRED API(무료 키) 로 실제 발표값 확인, 2차: Finnhub economic calendar(무료 티어)
- 애프터마켓 실적: yfinance `earnings_dates` 또는 Finnhub earnings calendar
### 3.4 저장 (SQLite)
- `snapshots(date, symbol, close, chg_pct, ...)` — 전일·주간 비교("연속 3일 하락" 등) 가능
- `reports(date, json, markdown, sent_at, channel)` — 중복 발송 방지(idempotency) 및 아카이브
## 4. 요약 (MiniMax API)
> 2026-08-25 확정: 요약 LLM은 **MiniMax** 사용. `.env`의 `MINIMAX_API_KEY`(Coding/Token Plan 키, `sk-cp-…`)로
> 글로벌 엔드포인트 `https://api.minimax.io` 에서 M3/M2.7/M2.5 호출 및 JSON Schema 출력·`reasoning_split` 동작을 실제 호출로 검증함.
> (중국 엔드포인트 `api.minimaxi.com` 은 이 키로 401 → 사용 금지)
- **엔드포인트**: OpenAI 호환 `POST https://api.minimax.io/v1/chat/completions`
- 클라이언트: `openai` SDK에 `base_url="https://api.minimax.io/v1"` 지정 (재시도·타임아웃 내장). 순수 `httpx` 로 대체 가능
- Anthropic 호환 `…/anthropic/v1/messages` 도 동작하지만, 한 가지로 통일 → OpenAI 호환 채택
- **모델**: 기본 `MiniMax-M3` (1M 컨텍스트, `thinking` 제어 가능, 프롬프트 캐시 `cached_tokens` 확인됨)
- 장애/오류 시 폴백: `MiniMax-M2.7` → `MiniMax-M2.5` (설정으로 순서 변경)
- M2.x 는 thinking 비활성 불가 → 반드시 `reasoning_split` 사용
- **필수 요청 파라미터**
- `reasoning_split: true` → 추론이 `message.reasoning_content` 로 분리되고 `content` 에는 답만 남음
(미설정 시 `content` 안에 `<think>…</think>` 가 섞여 나옴 — 방어적으로 `<think>` 블록 strip 유틸도 둠)
- `response_format: {"type":"json_schema","json_schema":{"name":…,"schema":…}}` → Pydantic 모델의 `model_json_schema()` 를 그대로 사용, 응답은 `model_validate_json` 으로 검증
- `max_completion_tokens`: 8192 (리포트 JSON + 추론 여유). M3 권장 상한 131072
- M3 한정 `thinking: {"type":"adaptive"}` (기본) — 요약 품질 위해 켜둠. 비용/속도 필요 시 `disabled`
- **프롬프트 캐시**: 고정 시스템 프롬프트(역할·출력 규칙·용어집·스키마 설명)를 앞에, 당일 데이터는 user 메시지 뒤에 → `prompt_tokens_details.cached_tokens` 로 적중 확인
- **LLM 계층 추상화**: `us2kr/llm/base.py` 에 `summarize(payload) -> ReportSchema` 인터페이스, `llm/minimax.py` 구현.
나중에 Claude/OpenAI 로 바꾸려면 구현체만 추가 (프롬프트·스키마는 공용)
- 리포트 스키마(초안):
```
headline: str # 한 줄 총평
market_overview: str # 지수/섹터 흐름 3~5문장
key_drivers: list[str] # 상승/하락 요인
rates_fx_commodities: str # 금리·달러·원화·유가
korea_implications: list[str] # 반도체/SOX, 환율, ADR, EWY → 한국 장 시사점
watchlist_today: list[str] # 오늘 한국 장에서 볼 것(지표 발표, 실적, 이벤트)
risk_flags: list[str] # 주의사항
data_gaps: list[str] # 수집 실패 항목(투명성)
```
- 프롬프트 원칙: 숫자는 제공 데이터만 사용(환각 금지), 확인 안 된 뉴스는 "보도에 따르면" 표기,
투자 조언 아닌 정보 요약 톤, 출력 언어 한국어
- 비용: Coding/Token Plan 구독 키 → 토큰당 과금 없이 플랜 쿼터 소모. 1일 1~2회 호출(입력 ~15K/출력 ~3K 토큰)이라 쿼터 부담 미미.
레이트리밋·쿼터 초과(429) 시 지수 백오프 재시도 후 폴백 모델
## 5. 발송
- 1차: **Telegram Bot** (`python-telegram-bot` 또는 raw HTTPS) — 설정 가장 단순, 모바일 알림
- 선택: Slack Incoming Webhook, 이메일(SMTP), 로컬 `reports/YYYY-MM-DD.md`
- Telegram 4096자 제한 → 섹션 단위 분할 전송, 또는 요약본 + 전체본 파일 첨부
- 실패 알림: 파이프라인 예외·데이터 전부 누락 시 운영자 채널로 에러 메시지
## 6. 상주 실행 구조
```
[daemon.py] APScheduler(BackgroundScheduler)
├─ 매일 00:05 ET: 캘린더 조회 → 오늘 XNYS 세션 있으면 close+75m 에 run_briefing 예약
├─ (선택) 08:20 KST: run_premarket_update
├─ 매시간: heartbeat(로그/헬스파일)
└─ 예외 → 재시도(3회, 지수 백오프) → 실패 알림
```
- **프로세스 감시자는 OS에 맡김**: macOS `launchd`(KeepAlive) / Linux `systemd`(Restart=always) / Docker(`restart: unless-stopped`)
- 재시작 후에도 중복 발송 방지: `reports` 테이블에 당일 기록 있으면 스킵
- 시각 판단은 항상 "다음 실행 시각 계산" 방식 → 프로세스가 죽었다가 살아나도 놓친 브리핑은 `catch-up` 정책(마감 후 3시간 이내면 즉시 실행, 아니면 스킵)
- 로깅: `logging` + 일자별 회전 파일, 수집 원본 JSON도 날짜별 보관(디버깅용)
## 7. 프로젝트 구조
```
US2KR/
├── pyproject.toml # deps: openai(MiniMax OpenAI-호환 클라이언트), yfinance, feedparser, exchange_calendars,
│ # apscheduler, pydantic, pydantic-settings, python-dotenv
├── .env.example # MINIMAX_API_KEY, MINIMAX_BASE_URL(=https://api.minimax.io/v1), MINIMAX_MODEL(=MiniMax-M3),
│ # TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, FRED_API_KEY ...
├── us2kr/
│ ├── config.py # pydantic-settings, 심볼 목록, 실행 오프셋 등
│ ├── market_time.py # exchange_calendars 래퍼: 세션/휴장/다음 실행 시각
│ ├── collectors/
│ │ ├── base.py # Collector 프로토콜, 타임아웃·재시도·실패 격리
│ │ ├── prices.py # yfinance (+fallback)
│ │ ├── news.py # RSS
│ │ └── events.py # 경제지표·실적 일정
│ ├── store.py # SQLite (snapshots, reports)
│ ├── llm/
│ │ ├── base.py # LLM 인터페이스 + ReportSchema(Pydantic)
│ │ ├── minimax.py # OpenAI-호환 호출, reasoning_split, json_schema, 폴백
│ │ └── prompts.py # 시스템 프롬프트(캐시 대상)·유저 템플릿
│ ├── summarize.py # payload 구성 → llm.summarize → 검증
│ ├── render.py # JSON → Markdown/Telegram 포맷
│ ├── deliver/
│ │ ├── telegram.py
│ │ ├── slack.py
│ │ └── file.py
│ ├── pipeline.py # run_briefing(date): collect → store → summarize → render → deliver
│ ├── daemon.py # 스케줄러 상주 루프
│ └── cli.py # us2kr run-once [--date] [--dry-run] | daemon | backfill | test-notify
├── deploy/
│ ├── com.us2kr.daemon.plist
│ ├── us2kr.service
│ └── Dockerfile / compose.yaml
├── tests/ # market_time(서머타임·휴장·조기폐장), render, 파이프라인(수집기 mock)
└── reports/ # 생성된 브리핑 아카이브
```
## 8. 구현 단계
| 단계 | 내용 | 완료 기준 |
|---|---|---|
| **P1 MVP** | config, prices collector, llm/minimax, summarize, render, `run-once --dry-run` | 터미널에서 오늘자 브리핑 Markdown 출력 |
| **P2 스케줄** | market_time, daemon, store(idempotency), catch-up | 서머타임·휴장·조기폐장 테스트 통과, 상주 실행 |
| **P3 데이터 확장** | news, events collector, 전일 대비 비교, data_gaps | 뉴스·일정 포함 리포트 |
| **P4 발송** | Telegram(+파일 아카이브), 실패 알림, 분할 전송 | 실제 폰으로 수신 |
| **P5 배포** | launchd/systemd/Docker, 로그 회전, README | 재부팅 후 자동 기동, 1주 무인 운영 |
| P6 (선택) | 08:20 개장 직전 업데이트, Slack/이메일, 주간 리뷰 | — |
## 9. 결정 필요 사항 (기본값으로 진행하되 바꿀 수 있음)
1. **발송 채널**: 기본 Telegram. Slack/이메일 원하면 P4에서 추가
2. **실행 환경**: 이 Mac에서 launchd로 상주 vs 별도 서버/Docker. 기본은 Mac launchd + Docker 파일 동봉
3. **데이터 유료화 여부**: 기본 무료(yfinance/RSS/FRED). 안정성 필요하면 Polygon/Finnhub 유료 티어
4. **리포트 톤/길이**: 기본 A4 1장 내 한국어. 영어 병기 여부
5. **08:20 프리마켓 업데이트** 포함 여부 (기본 미포함, 옵션)
## 10. 리스크와 대응
- yfinance 비공식 API → 스키마 변경/차단 가능: 수집기 추상화 + 2차 소스 + 실패 시에도 발행
- 서머타임 전환 주 오류 → `zoneinfo` + 캘린더 기반 계산, 전환일 단위 테스트
- LLM 환각(숫자) → 구조화 출력 + "제공 데이터만 사용" 규칙 + 렌더링 시 숫자는 원본 데이터에서 직접 삽입
- MiniMax `<think>` 누출 / JSON 깨짐 → `reasoning_split` 필수 + strip 유틸 + Pydantic 검증 실패 시 1회 재요청
- MiniMax 장애·429 → 모델 폴백(M3→M2.7→M2.5) → 그래도 실패면 LLM 없이 '숫자 요약표만' 발송(degraded mode)
- 중복/누락 발송 → SQLite 기록 기반 idempotency + catch-up 정책
- 키 유출 → `.env` + `.gitignore`, 로그에 시크릿 마스킹
---
## 11. 구현 현황 (2026-08-25)
P1~P5 코드 구현 완료. 검증: pytest 287개 통과, ruff 클린, 라이브 dry-run(MiniMax-M3 요약 포함) 정상, 데몬 기동/스케줄/SIGTERM 종료 확인.
| 영역 | 파일 | 상태 |
|---|---|---|
| 시간/캘린더 | `us2kr/market_time.py` | ✅ XNYS/XKRX, 조기폐장·DST·휴장 테스트, 임시휴장 override(`US2KR_KRX_EXTRA_HOLIDAYS`) |
| 수집 | `collectors/prices.py`, `news.py`, `events.py` | ✅ yfinance 배치 + 애프터마켓(1분봉), RSS 7개, FRED(키 필요)·실적 |
| 요약 | `llm/minimax.py`, `llm/prompts.py`, `summarize.py` | ✅ M3→M2.7→M2.5 폴백, `reasoning_split`, JSON Schema, 수리 재시도 |
| 저장/렌더 | `store.py`, `render.py` | ✅ SQLite(WAL), 히스토리 노트, Markdown + Telegram HTML 청크 |
| 발송 | `deliver/file.py`, `deliver/telegram.py` | ✅ 파일 아카이브 항상, Telegram은 토큰 설정 시 (아직 미설정) |
| 오케스트레이션 | `pipeline.py`, `daemon.py`, `cli.py`, `logging_setup.py` | ✅ 멱등성(저장→발송 순), catch-up, misfire 복구, 시크릿 마스킹 |
| 배포 | `deploy/` | ✅ launchd / systemd / Docker 파일 (미설치) |
남은 운영 작업:
1. `.env`에 `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID` 추가 → `us2kr test-notify`로 확인
2. (선택) `FRED_API_KEY` 추가 → 경제지표 섹션 활성화
3. `bash deploy/install-launchd.sh`로 상주 등록 (노트북이면 `pmset` 예약 기동 필요)
4. 첫 실제 브리핑(2026-08-26 06:15 KST) 결과 확인 후 프롬프트/길이 튠
알려진 제한: 실적 스캔은 15종목 상한(ADR 일부 미포함), 뉴스는 수집 시점 기준 상한 없음(정규 실행에는 무영향), LLM 산출 `data_gaps`는 렌더에 미표시.