""" Sprint 5 验证脚本 — 情绪因子快速验证。 用法: python cli/demo_sentiment.py python cli/demo_sentiment.py --ts_code 600519.SH python cli/demo_sentiment.py --ts_code 000001.SZ --no-qwen """ import sys, os, argparse sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import pandas as pd from data.data_manager import DataManager from factors.sentiment.news_source import NewsSource, align_news_to_trading_days from factors.sentiment.qwen_client import QwenClient from factors.sentiment.sentiment_engine import SentimentEngine def main(): p = argparse.ArgumentParser(description="Sprint 5 — 情绪因子快速验证") p.add_argument("--ts_code", default="000001.SZ", help="测试股票代码") p.add_argument("--no-qwen", action="store_true", help="跳过 Qwen 情绪分析") args = p.parse_args() print("=" * 60) print("Sprint 5 — Qwen 情绪因子验证") print("=" * 60) dm = DataManager(); dm.init_db() print("\n[1/5] 新闻数据源测试...") news_src = NewsSource(use_mcp=False) news_df = news_src.fetch(args.ts_code, max_news=10) if not news_df.empty: print(" 获取 {} 条新闻".format(len(news_df))) for _, row in news_df.head(3).iterrows(): print(" [{}] {}... (source={})".format(row["date"], str(row["title"])[:80], row["source"])) else: print(" (无新闻数据)") print("[OK]") print("\n[2/5] 日期对齐测试...") from data.data_manager import DataManager as DM price = dm.get_daily(args.ts_code) if dm.get_daily(args.ts_code) is not None else dm.get_daily("000001.SZ") if price is not None and not price.empty: price = price.set_index("trade_date").sort_index() daily_idx = pd.to_datetime(price.index, format="%Y%m%d", errors="coerce") test_news = pd.DataFrame({"date": ["20240601", "20240602", "20240603"], "title": ["周六新闻", "周日新闻", "周一新闻"], "content": [""] * 3, "source": ["test"] * 3, "url": [""] * 3}) aligned = align_news_to_trading_days(test_news, daily_idx) for _, row in aligned.iterrows(): print(" {}: {}".format(row["title"], row["date"])) print("[OK]") print("\n[3/5] Qwen 客户端状态...") client = QwenClient() has_api = bool(client.api_key) or bool(client.local_base_url) if has_api and not args.no_qwen: mode = "本地Ollama/{}".format(client.local_model) if client.local_base_url else "DashScope/{}".format(client.model) print(" 模式: {}".format(mode)) else: print(" 模式: 未配置或跳过") print("[OK]") print("\n[4/5] SentimentEngine 全链路 (max_news=10)...") try: sent_engine = SentimentEngine(dm, qwen_client=client, news_source=news_src) sent_df = sent_engine.compute(args.ts_code, max_news=10) if sent_df is not None and not sent_df.empty: valid = sent_df.dropna(how="all") print(" 情绪因子: {}".format(list(sent_df.columns))) print(" 有效行: {}/{}".format(len(valid), len(sent_df))) if not valid.empty: print(valid.tail(5).round(4).to_string()) except Exception as e: print(" [WARN] {}".format(e)) print("[OK]") print("\n[5/5] 分析范围解析...") scope = sent_engine.get_scope_stocks() print(" 成分股数量: {}".format(len(scope))) if scope: print(" 示例: {}".format(", ".join(scope[:5]))) print("[OK]") try: from reports.storage import save_report save_report("## 情绪因子验证 — {}\n\n- 新闻源: OK\n- 日期对齐: OK\n- Qwen: {}".format( args.ts_code, "就绪" if has_api else "未配置"), "情绪因子验证", subject_type="stock", subject_code=args.ts_code) print(" 报告已存入 DB") except Exception: pass print("\n" + "=" * 60) print("Sprint 5 验证完成") print("=" * 60) if __name__ == "__main__": main()