diff --git a/stock/app/holdings_intel.py b/stock/app/holdings_intel.py index 64500c8..43b01a3 100644 --- a/stock/app/holdings_intel.py +++ b/stock/app/holdings_intel.py @@ -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 (우선순위 순). diff --git a/stock/app/test_holdings_intel.py b/stock/app/test_holdings_intel.py index c099bff..9227efc 100644 --- a/stock/app/test_holdings_intel.py +++ b/stock/app/test_holdings_intel.py @@ -203,3 +203,13 @@ def test_market_events_detects_move_and_volume(): types = {e["type"] for e in evts} assert "price_move" in types assert "volume_surge" in types + + +def test_news_issues_flags_negative_sentiment(monkeypatch): + # news_sentiment: 005930 음수 점수 → 악재 flag + monkeypatch.setattr(hi, "_news_sentiment_map", lambda date: { + "005930": {"score_raw": -0.6, "news_count": 8}}) + issues = hi.news_issues(["005930"], date="2026-05-29", use_llm=False) + assert "005930" in issues + assert issues["005930"][0]["type"] == "news" + assert issues["005930"][0]["severity"] in ("med", "high")