Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/analysis/sentiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion backend/tools/m27_sentiment_cache_backfill.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": []}
Expand Down
25 changes: 25 additions & 0 deletions tests/test_news_sentiment_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading