From e60ecb8bde8ce6bcec6423e6ada7ebe59335ac56 Mon Sep 17 00:00:00 2001 From: whdwo Date: Wed, 17 Jun 2026 18:34:38 +0900 Subject: [PATCH] Add global market context to morning analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetch_global_data(): Yahoo Finance로 나스닥/S&P500/다우/SOX/달러원/WTI/미국10년물 수집 - morning.py --print 출력에 global_raw 포함 - morning.md: global_score·global_risk 산출 기준 및 섹터 힌트 매핑 추가 - daily_context.json에 global_context 블록 추가 (domestic_score와 분리) - sentiment_score = domestic_score×0.6 + global_score×0.4 Co-Authored-By: Claude Sonnet 4.6 --- .claude/commands/morning.md | 70 +++++++++++++++++++++++++++++-------- app/ai/morning.py | 53 ++++++++++++++++++++++++++-- app/requirements.txt | 1 + 3 files changed, 106 insertions(+), 18 deletions(-) diff --git a/.claude/commands/morning.md b/.claude/commands/morning.md index 8879c7c..8a68f32 100644 --- a/.claude/commands/morning.md +++ b/.claude/commands/morning.md @@ -10,23 +10,48 @@ python app/ai/morning.py --print ``` 위 명령을 실행해 다음 데이터를 수집한다: +- **글로벌 지표 (global_raw)**: 나스닥·S&P500·다우·SOX·달러/원·WTI·미국10년물 - **RSS 뉴스**: 한경증권·한경경제·파이낸셜뉴스·매경증권 4개 언론사 (~80건) - **KIS 수급**: 거래량 상위 30종목, 외국인/기관 순매수 상위 10종목, 업종 동향 - **종목별 뉴스**: 네이버 검색 API로 거래량 상위 20종목 각 5건 -### 2. 분석 -수집된 데이터를 바탕으로 다음 항목을 판단한다: +### 2. 글로벌 분석 (global_raw → global_context) +수집된 `global_raw`를 바탕으로 글로벌 점수와 리스크를 산출한다. + +**global_score 산출 기준 (0~100, 50=중립):** +- 나스닥·SOX 등락이 가장 큰 가중치 (한국 반도체·기술주 직접 영향) +- S&P500·다우 보조 +- USD/KRW 상승(달러 강세)은 외국인 매도 압력 → 감점 +- WTI 급등은 항공·운송 부담 → 소폭 감점 +- 미국 10년물 금리 급등은 성장주 할인 → 감점 + +**global_risk 판단:** +- global_score >= 60 → 낮음 +- global_score 40~59 → 보통 +- global_score < 40 → 높음 + +**섹터 힌트 매핑 (hot/avoid 섹터에 반영):** +- SOX < -2% → 반도체·AI 관련 보수적, hot_sectors에서 제외 또는 주의 표시 +- SOX > +1% → 반도체 hot_sectors 강화 +- WTI > +2% → 항공 avoid, 정유/해운 관심 +- WTI < -2% → 정유 보수적, 항공 우호 +- USD/KRW > +0.5% → 전체 포지션 배율 축소 압력 +- 미국장 전반 급락(나스닥 < -2%) → position_size_multiplier 하향 + +### 3. 국내 분석 +수집된 뉴스·수급 데이터를 바탕으로 다음 항목을 판단한다: - **시장 분위기**: 강세 / 중립 / 약세 -- **감성 점수**: 0~100 (50=중립, 70이상=강세, 30이하=약세) +- **domestic_score**: 0~100 (50=중립, 70이상=강세, 30이하=약세) - **리스크 레벨**: 낮음 / 보통 / 높음 -- **주목 섹터**: 수급·뉴스 모두 긍정적인 섹터 -- **회피 섹터**: 악재·수급 부진 섹터 +- **주목 섹터**: 수급·뉴스 모두 긍정적인 섹터 (글로벌 힌트 반영) +- **회피 섹터**: 악재·수급 부진 섹터 (글로벌 힌트 반영) - **boosted_tickers**: 거래량 상위 + 외국인 순매수 겹치는 종목코드 - **blacklist_tickers**: 종목별 뉴스에서 악재(횡령·소송·거래정지 등) 감지된 종목코드 -- **position_size_multiplier**: 0.5(약세) ~ 1.0(중립) ~ 1.5(강세) +- **sentiment_score**: domestic_score와 global_score를 6:4로 합산한 최종 점수 +- **position_size_multiplier**: 0.5(약세) ~ 1.0(중립) ~ 1.5(강세), 글로벌 리스크 반영 - **trade_allowed**: sentiment_score < 40이면 false -### 3. daily_context.json 저장 +### 4. daily_context.json 저장 분석 결과를 `data/daily_context.json`에 저장한다. 형식: ```json { @@ -34,23 +59,38 @@ python app/ai/morning.py --print "generated_at": "HH:MM:SS", "trade_allowed": true, "market_sentiment": "중립", - "sentiment_score": 62, - "risk_level": "보통", - "hot_sectors": ["반도체", "2차전지"], - "avoid_sectors": ["금융", "건설"], - "boosted_tickers": ["005930", "000660"], + "sentiment_score": 55, + "domestic_score": 62, + "global_context": { + "nasdaq_change": -1.15, + "sp500_change": -0.57, + "dow_change": 0.64, + "sox_change": -5.71, + "usdkrw_change": 0.0, + "wti_change": -1.21, + "us10y": 4.43, + "global_score": 32, + "global_risk": "높음" + }, + "risk_level": "높음", + "hot_sectors": ["방산", "2차전지"], + "avoid_sectors": ["반도체", "항공"], + "boosted_tickers": [], "blacklist_tickers": [], - "position_size_multiplier": 1.0, + "position_size_multiplier": 0.7, "reason": "50자 이내 시장 요약" } ``` -### 4. Discord 알림 전송 +**sentiment_score 계산 예시:** +- domestic_score=62, global_score=32 → sentiment_score = 62×0.6 + 32×0.4 = 50 + +### 5. Discord 알림 전송 아래 명령을 실행해 분석 결과를 Discord로 전송한다: ```bash python app/ai/morning.py --send-discord ``` -### 5. 완료 +### 6. 완료 분석 요약을 한 줄로 출력하고 종료한다. 이 명령 안에서는 `/start-bot` 또는 `python scripts/start_bot.py`를 실행하지 않는다. diff --git a/app/ai/morning.py b/app/ai/morning.py index 426004c..e1be785 100644 --- a/app/ai/morning.py +++ b/app/ai/morning.py @@ -84,6 +84,49 @@ RSS_FEEDS = [ ] +# ── 글로벌 지표 수집 ────────────────────────────────────────────────────────── + +def fetch_global_data() -> dict: + """Yahoo Finance로 미국 주요 지수·환율·원자재 전일 종가 및 등락률 수집""" + try: + import yfinance as yf + except ImportError: + logger.warning("yfinance 미설치 — 글로벌 데이터 스킵") + return {} + + SYMBOLS = { + "nasdaq": "^IXIC", + "sp500": "^GSPC", + "dow": "^DJI", + "sox": "^SOX", + "usdkrw": "KRW=X", + "wti": "CL=F", + "us10y": "^TNX", + } + + result: dict = {} + for name, sym in SYMBOLS.items(): + try: + hist = yf.Ticker(sym).history(period="2d") + if len(hist) >= 2: + prev = float(hist["Close"].iloc[-2]) + last = float(hist["Close"].iloc[-1]) + result[name] = { + "price": round(last, 2), + "change_pct": round((last - prev) / prev * 100, 2), + } + elif len(hist) == 1: + result[name] = { + "price": round(float(hist["Close"].iloc[-1]), 2), + "change_pct": None, + } + except Exception as e: + logger.warning(f"글로벌 데이터 실패 [{name}/{sym}]: {e}") + + logger.info(f"글로벌 데이터 수집: {list(result.keys())}") + return result + + # ── RSS 뉴스 수집 ───────────────────────────────────────────────────────────── async def fetch_rss_news() -> list[str]: @@ -223,10 +266,13 @@ async def main(print_mode: bool = False): for d in ["data/news", "data/market"]: os.makedirs(d, exist_ok=True) - # 1. RSS 뉴스 수집 (4개 언론사) + # 1. 글로벌 지표 수집 (Yahoo Finance — 동기, 블로킹 짧음) + global_data = fetch_global_data() + + # 2. RSS 뉴스 수집 (4개 언론사) news = await fetch_rss_news() - # 2. KIS 시장 데이터 — 데이터 수집 전용이므로 실거래 API 사용 (주문 없음) + # 3. KIS 시장 데이터 — 데이터 수집 전용이므로 실거래 API 사용 (주문 없음) _orig_mock = os.environ.get("KIS_MOCK", "true") os.environ["KIS_MOCK"] = "false" kis = KISClient() @@ -238,7 +284,7 @@ async def main(print_mode: bool = False): except Exception as e: logger.warning(f"KIS 수집 실패: {e}") - # 3. 네이버 종목별 뉴스 (거래량 상위 20종목) + # 4. 네이버 종목별 뉴스 (거래량 상위 20종목) stock_news = await fetch_stock_news_naver(market["volume_rank"]) # 파일 저장 @@ -257,6 +303,7 @@ async def main(print_mode: bool = False): print(json.dumps( { "date": TODAY, + "global_raw": global_data, # 미국 지수·환율·원자재 "news_headlines": news, # RSS 전체 (~80건) "volume_rank": market["volume_rank"][:20], "foreign_buy_top10": market["foreign_buy"], diff --git a/app/requirements.txt b/app/requirements.txt index e56db64..cd1f099 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -11,3 +11,4 @@ pykrx==1.0.48 finance-datareader==0.9.94 scikit-learn==1.5.1 joblib==1.4.2 +yfinance>=0.2.40