Initial commit: cc-cursor 全链路量化研究平台

7 Sprints 全部完成:
  Sprint 0: 基础设施 (DataManager + MariaDB)
  Sprint 1: 因子引擎 (34因子/12分类)
  Sprint 2: VectorBT 回测 (5策略+截面)
  Sprint 3: Optuna 优化 (+Walk-Forward)
  Sprint 4: ML 模型 (LightGBM+CatBoost)
  Sprint 5: Qwen 情绪因子 (三源新闻+日期对齐)
  Sprint 6: Agent 系统 (4Agent+日报.md/.html)

生产加固 (15项): Tushare双源fallback, SSH自动恢复, pool_pre_ping,
  save_daily先删后插, load_dotenv绝对路径, 日报5d/20d修复,
  RiskAgent改上证指数, 昨日对比+数据截止, mac_report utf8mb4,
  CLAUDE-*.md 9条已知Bug, demo全参数化, djapi数据源归一化,
  indexDatas API修正

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 15:59:05 +08:00
co-authored by Claude Opus 4.7
commit 271a9343a5
293 changed files with 59598 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python
"""
Agent 命令行入口。
用法:
python cli/agent_cli.py daily # 执行每日流程
python cli/agent_cli.py picks [N] # 今日选股 Top N
python cli/agent_cli.py risk # 风险评估
python cli/agent_cli.py research # 因子研究
python cli/agent_cli.py report [DATE] # 生成日报
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from datetime import datetime
def init_engines():
"""初始化所有引擎。"""
from data.data_manager import DataManager
from factors.engine import FactorEngine
from backtest.vectorbt.engine import VectorBTEngine
from optimizer.engine import OptunaEngine
from factors.sentiment.sentiment_engine import SentimentEngine
from factors.sentiment.news_source import NewsSource
from factors.sentiment.qwen_client import QwenClient
from factors.registry import get_factor
dm = DataManager()
dm.init_db()
fe = FactorEngine(dm)
bt = VectorBTEngine()
opt = OptunaEngine(bt)
sent = SentimentEngine(dm, qwen_client=QwenClient(), news_source=NewsSource())
fe._sentiment_engine = sent
return {
"dm": dm,
"fe": fe,
"bt": bt,
"opt": opt,
"sent": sent,
}
def main():
if len(sys.argv) < 2:
print("用法: agent_cli.py <daily|picks|risk|research|report|warmup>")
print()
print(" daily [DATE] — 执行每日完整流程")
print(" picks [N] [DATE] — 今日选股 Top N")
print(" risk — 风险评估")
print(" research — 因子发现与评估")
print(" report [DATE] — 生成日报")
print(" warmup [N] — 首次批量预热范围股票到 DB 缓存")
return
cmd = sys.argv[1]
engines = init_engines()
from agents.orchestrator import AgentOrchestrator
orch = AgentOrchestrator(**engines)
orch.setup()
if cmd == "daily":
date = sys.argv[2] if len(sys.argv) > 2 else None
results = orch.run_daily(date=date)
# 打印日报内容
report_md = results.get("report", {}).get("report_markdown", "")
if report_md:
print(report_md)
elif cmd == "picks":
n = int(sys.argv[2]) if len(sys.argv) > 2 else 15
date = sys.argv[3] if len(sys.argv) > 3 else None
result = orch.picks(date=date, top_n=n)
print(f"\n选股结果 ({result.get('date', '?')}):")
for p in result.get("top_picks", []):
print(f" {p['ts_code']:12s} {p.get('name', ''):10s} {p['score']:.4f}")
elif cmd == "risk":
result = orch.risk_check()
print(f"\n风险评估:")
print(f" 等级: {result['risk_level']}")
print(f" 建议仓位: {result['target_exposure']:.0%}")
print(f" 止损线: {result['stop_loss']:.0%}")
print(f" 单票上限: {result['max_single_position']:.0%}")
indicators = result.get("indicators", {})
if indicators:
print(f" 波动率: {indicators.get('market_volatility', 0):.1f}%")
print(f" 回撤: {indicators.get('current_drawdown', 0):.1f}%")
for a in result.get("alerts", []):
print(f" ⚠️ {a}")
elif cmd == "research":
result = orch.run_research_cycle()
top = result.get("research", {}).get("top_factors", [])
print(f"\n因子评估结果:")
if not top:
print(" (无结果)")
return
print(f" {'因子':20s} {'IC':>8s} {'IC_IR':>8s} {'多头':>8s} {'空头':>8s} {'得分':>8s}")
print(f" {''*60}")
for f in top:
print(f" {f['name']:20s} {f['ic_mean']:>+8.4f} {f['icir']:>8.3f} "
f"{f['long_ret']:>+7.1f}% {f['short_ret']:>+7.1f}% {f['score']:>8.4f}")
elif cmd == "warmup":
batch_n = int(sys.argv[2]) if len(sys.argv) > 2 else 50
print("首次批量预热: 每次 {} 只股票,分批执行...".format(batch_n))
sent = engines.get("sent")
dm = engines.get("dm")
scope = sent.get_scope_stocks() if sent else list(dm.get_stock_list().index[:100])
from database.dao import get_latest_trade_date
uncached = [c for c in scope if not get_latest_trade_date(c)]
print("范围: {} 只, 未缓存: {}".format(len(scope), len(uncached)))
total_synced = 0
for i in range(0, len(uncached), batch_n):
batch = uncached[i:i + batch_n]
print("[warmup] 批次 {}/{} ({}~{})".format(i // batch_n + 1, (len(uncached) - 1) // batch_n + 1, i, i + len(batch)))
for ts_code in batch:
try:
n = dm.sync_daily(ts_code)
total_synced += n
except Exception as e:
print(" {} 失败: {}".format(ts_code, e))
print(" 累计同步: {}".format(total_synced))
print("预热完成: {} 条数据, {} 只新股票已缓存".format(total_synced, len(uncached)))
elif cmd == "report":
date = sys.argv[2] if len(sys.argv) > 2 else None
result = orch.generate_report(date=date)
print("\n日报已生成: {}".format(result.get("report_path", "?")))
# 存入 DB
md = result.get("report_markdown", "")
if md:
from reports.storage import save_report
save_report(md, "量化日报", report_date=date or datetime.now().strftime("%Y%m%d"),
subject_type="daily", subject_code="")
print(" 已存入 DB")
if md:
print(md)
else:
print(f"未知命令: {cmd}")
if __name__ == "__main__":
main()