diff --git a/backend/analysis/sentiment.py b/backend/analysis/sentiment.py index e08db39..4df4075 100644 --- a/backend/analysis/sentiment.py +++ b/backend/analysis/sentiment.py @@ -209,7 +209,7 @@ def analyze_news( tool=_SENTIMENT_TOOL, system=SYSTEM_PROMPT, max_tokens=300, - model_tier="fast", + model_tier=settings.sentiment_model_tier, ) try: import json as _json diff --git a/backend/config.py b/backend/config.py index 79b0a39..86f1a36 100644 --- a/backend/config.py +++ b/backend/config.py @@ -111,6 +111,12 @@ class Settings(BaseSettings): # variant tested (-0.0027 / -0.0146 / -0.0280), i.e. the override only subtracts IC. # Kept behind a flag so it can be re-enabled for a clean single-provider OOS re-test. sentiment_event_override_enabled: bool = False + # Model tier for news sentiment scoring. "capable" → claude-sonnet-4-6 on every + # provider. The 2026-06-15 clean single-provider OOS measured sentiment IC 0.0735 + # under sonnet-4.6 vs ~0.020 under the "fast"/Codex tier — provider quality is the + # dominant lever for this signal. NOTE: with AI_PROVIDER=local_cli also set + # LOCAL_CLI_PREFER_CODEX=false, otherwise the Codex path ignores this model. + sentiment_model_tier: str = "capable" # Signal profile: legacy Qlib framework or current new framework. paper_trading_profile: str = "auto" # auto / test1_legacy_qlib / new_framework diff --git a/backend/tools/m27_sentiment_cache_backfill.py b/backend/tools/m27_sentiment_cache_backfill.py index 788468d..55cb378 100644 --- a/backend/tools/m27_sentiment_cache_backfill.py +++ b/backend/tools/m27_sentiment_cache_backfill.py @@ -170,13 +170,15 @@ def _call_llm_sentiment(titles: list[str], symbol: str) -> dict[str, Any]: if not has_runtime_llm_provider(): readiness = runtime_readiness() raise RuntimeError(f"runtime LLM provider is not usable: {readiness.get('reason')}") + from backend.config import settings + prompt = f"股票代码:{symbol}\n新闻标题:\n" + "\n".join(f"- {title}" for title in titles[:15]) data = get_provider().complete_structured( prompt=prompt, tool=_SENTIMENT_TOOL, system=SYSTEM_PROMPT, max_tokens=300, - model_tier="fast", + model_tier=settings.sentiment_model_tier, ) if not data: data = {"sentiment": 0.0, "summary": "解析失败", "impact": "short", "key_events": []} diff --git a/tests/test_news_sentiment_pack.py b/tests/test_news_sentiment_pack.py index 401ce50..3e979cf 100644 --- a/tests/test_news_sentiment_pack.py +++ b/tests/test_news_sentiment_pack.py @@ -199,3 +199,28 @@ def fake_analyze_news(titles, symbol, company_aliases=None): assert captured["symbol"] == "603986" assert captured["company_aliases"] == ["兆易创新", "603986"] assert "兆易创新发布业绩预增公告" in captured["titles"] + + +def test_analyze_news_uses_configured_sentiment_model_tier(monkeypatch): + # Sentiment must score with the configured tier (default "capable" → sonnet-4.6), + # not the hardcoded "fast" tier. Clean OOS measured IC 0.0735 (sonnet) vs ~0.02 (fast). + from backend.config import settings + + monkeypatch.setattr(sentiment, "has_runtime_llm_provider", lambda *_a, **_k: True) + monkeypatch.setattr(sentiment, "_cache_get", lambda *_a, **_k: None) + monkeypatch.setattr(sentiment, "_persistent_cache_get", lambda *_a, **_k: None) + monkeypatch.setattr(sentiment, "_cache_set", lambda *_a, **_k: None) + monkeypatch.setattr(sentiment, "_persistent_cache_set", lambda *_a, **_k: None) + monkeypatch.setattr(settings, "sentiment_model_tier", "capable") + + captured = {} + + class _Prov: + def complete_structured(self, **kwargs): + captured["model_tier"] = kwargs.get("model_tier") + return {"sentiment": 0.3, "summary": "ok", "impact": "short", "key_events": []} + + monkeypatch.setattr(sentiment, "get_provider", lambda: _Prov()) + + sentiment.analyze_news(["兆易创新发布业绩预增公告"], symbol="603986") + assert captured["model_tier"] == "capable"