- deep_learning.py: INPUT_SIZE=7 (close/open/high/low/volume/rsi/macd), feature_scaler/target_scaler 분리, ModelRegistry LRU 종목별 격리 (v3 체크포인트) - kis.py: get_daily_ohlcv() OHLCV 전체 반환, KISAsyncClient 비동기 배치 조회 추가, order() 지정가/조건부 주문 지원 - strategy/process.py: ATR/ADX 기반 동적 손절익절, 트레일링 스탑, 포지션 사이징 강화 - config.py: OLLAMA_NUM_THREAD=8 (9800X3D 최적화), LSTM_COOLDOWN/FAST_EPOCHS 환경변수화 - macro.py: 거시경제 지표 계산 개선 - ollama.py: VRAM 여유량 기반 선택적 언로드 - monitor.py: CPU 서킷 브레이커 연속 횟수 조건 추가 - ipc.py: IPC_STALENESS 600초로 확대 - news.py: 비동기 뉴스 수집 개선 - telegram.py, runner.py: 안정성 개선 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
4.0 KiB
Python
106 lines
4.0 KiB
Python
import time
|
|
import requests
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
class NewsCollector:
|
|
"""동기 뉴스 수집 (Google News RSS)"""
|
|
@staticmethod
|
|
def get_market_news(query="주식 시장"):
|
|
url = f"https://news.google.com/rss/search?q={query}&hl=ko&gl=KR&ceid=KR:ko"
|
|
try:
|
|
resp = requests.get(url, timeout=5)
|
|
root = ET.fromstring(resp.content)
|
|
items = []
|
|
for item in root.findall(".//item")[:5]:
|
|
title = item.find("title").text
|
|
items.append({"title": title, "source": "Google News"})
|
|
return items
|
|
except Exception as e:
|
|
print(f"[News] Collection failed: {e}")
|
|
return []
|
|
|
|
|
|
class AsyncNewsCollector:
|
|
"""비동기 뉴스 수집 + 5분 캐싱"""
|
|
|
|
def __init__(self):
|
|
self._cache = None
|
|
self._cache_time = 0
|
|
self._cache_ttl = 300 # 5분
|
|
self._stock_cache = {} # {stock_name: (items, timestamp)}
|
|
|
|
def get_market_news(self, query="주식 시장"):
|
|
"""동기 인터페이스 (하위 호환)"""
|
|
now = time.time()
|
|
if self._cache and (now - self._cache_time) < self._cache_ttl:
|
|
return self._cache
|
|
|
|
result = NewsCollector.get_market_news(query)
|
|
self._cache = result
|
|
self._cache_time = now
|
|
return result
|
|
|
|
async def get_market_news_async(self, query="주식 시장"):
|
|
"""비동기 뉴스 수집 (aiohttp + 캐싱)"""
|
|
now = time.time()
|
|
if self._cache and (now - self._cache_time) < self._cache_ttl:
|
|
return self._cache
|
|
|
|
try:
|
|
import aiohttp
|
|
url = f"https://news.google.com/rss/search?q={query}&hl=ko&gl=KR&ceid=KR:ko"
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
|
content = await resp.read()
|
|
root = ET.fromstring(content)
|
|
items = []
|
|
for item in root.findall(".//item")[:5]:
|
|
title = item.find("title").text
|
|
items.append({"title": title, "source": "Google News"})
|
|
|
|
self._cache = items
|
|
self._cache_time = now
|
|
return items
|
|
except ImportError:
|
|
return self.get_market_news(query)
|
|
except Exception as e:
|
|
print(f"[News Async] Collection failed: {e}")
|
|
if self._cache:
|
|
return self._cache
|
|
return self.get_market_news(query)
|
|
|
|
async def get_stock_news_async(self, stock_name, max_items=3):
|
|
"""종목별 뉴스 수집 (5분 캐싱)
|
|
stock_name: 종목 이름 (e.g. '삼성전자', 'SK하이닉스')
|
|
"""
|
|
now = time.time()
|
|
cached = self._stock_cache.get(stock_name)
|
|
if cached and (now - cached[1]) < self._cache_ttl:
|
|
return cached[0]
|
|
|
|
try:
|
|
import aiohttp
|
|
import urllib.parse
|
|
query = urllib.parse.quote(f"{stock_name} 주가")
|
|
url = f"https://news.google.com/rss/search?q={query}&hl=ko&gl=KR&ceid=KR:ko"
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
|
content = await resp.read()
|
|
root = ET.fromstring(content)
|
|
items = []
|
|
for item in root.findall(".//item")[:max_items]:
|
|
title_el = item.find("title")
|
|
if title_el is not None and title_el.text:
|
|
items.append({"title": title_el.text, "source": "Google News"})
|
|
|
|
self._stock_cache[stock_name] = (items, now)
|
|
return items
|
|
except Exception as e:
|
|
print(f"[News] 종목 뉴스 수집 실패 ({stock_name}): {e}")
|
|
return []
|
|
|
|
def clear_stock_cache(self):
|
|
"""종목 뉴스 캐시 전체 초기화"""
|
|
self._stock_cache.clear()
|