feat(stock): news_issues (감성 기반 악재 flag)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 22:02:45 +09:00
parent 241c24943f
commit 4ed3794f71
2 changed files with 47 additions and 0 deletions

View File

@@ -172,6 +172,43 @@ def market_events(ticker: str, ticker_prices: "pd.DataFrame",
return events
# ---- Task 3.2: news_issues ----
NEG_SENTIMENT = -0.3 # 이하면 악재 후보
def _news_sentiment_map(date: str) -> dict:
"""date 기준 news_sentiment 테이블에서 ticker → {score_raw, news_count} 맵 반환."""
with db._conn() as conn:
try:
rows = conn.execute(
"SELECT ticker, score_raw, news_count FROM news_sentiment WHERE date=?",
(date,),
).fetchall()
except Exception:
return {}
return {r["ticker"]: {"score_raw": r["score_raw"], "news_count": r["news_count"]}
for r in rows}
def news_issues(tickers: list[str], date: str, use_llm: bool = True) -> dict[str, list]:
"""news_sentiment 음수 → 악재 flag. (LLM 요약은 best-effort; 단위 테스트는 use_llm=False로.)"""
senti = _news_sentiment_map(date)
out: dict[str, list] = {}
for t in tickers:
s = senti.get(t)
if not s or s["score_raw"] is None:
continue
if s["score_raw"] <= NEG_SENTIMENT:
sev = "high" if s["score_raw"] <= NEG_SENTIMENT * 2 else "med"
out.setdefault(t, []).append({
"type": "news",
"severity": sev,
"summary": f"부정 뉴스 감성({s['score_raw']:+.2f}, {s.get('news_count', 0)}건)",
})
return out
def decide_action(tech_score: float, exit_flags: dict, pnl: float | None,
add_score: float = ADD_SCORE) -> tuple[str, str]:
"""액션 결정 매트릭스: sell > trim > add > hold (우선순위 순).