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 (우선순위 순).

View File

@@ -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")