Files
myquant/finance/agents/orchestrator.py
T
simonandClaude Opus 4.7 271a9343a5 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>
2026-06-07 15:59:05 +08:00

212 lines
7.5 KiB
Python

"""
AgentOrchestrator — Agent 编排器。
管理所有 Agent 的生命周期、执行顺序、结果传递。
"""
from datetime import datetime
from agents.research_agent import ResearchAgent
from agents.selection_agent import SelectionAgent
from agents.risk_agent import RiskAgent
from agents.report_agent import ReportAgent
class AgentOrchestrator:
"""
Agent 编排器。
用法:
orch = AgentOrchestrator(dm=dm, fe=engine_fe, bt=engine_bt, ...)
orch.setup() # 注册所有 Agent
orch.run_daily() # 执行每日流程
"""
def __init__(self, **engines):
self.engines = engines
self.agents: dict = {}
self._last_results: dict = {}
def setup(self):
"""注册所有 Agent。"""
self.agents["research"] = ResearchAgent(**self.engines)
self.agents["selection"] = SelectionAgent(**self.engines)
self.agents["risk"] = RiskAgent(**self.engines)
self.agents["report"] = ReportAgent(**self.engines)
print(f"[Orchestrator] 已注册 {len(self.agents)} 个 Agent: {list(self.agents)}")
# ── 每日流程 ──────────────────────────────────────────
def run_daily(self, date: str | None = None) -> dict:
"""
每日任务流:
1. 同步数据
2. 风险评估
3. 股票打分
4. 生成日报
"""
date = date or datetime.now().strftime("%Y%m%d")
print(f"\n{'='*60}")
print(f"[Orchestrator] 每日流程 — {date}")
print(f"{'='*60}")
results = {"date": date}
# Step 1: 增量同步已缓存股票的最新行情
print("\n[Step 1/4] 同步行情...")
dm = self.engines.get("dm")
sent = self.engines.get("sent")
from database.dao import get_latest_trade_date
scope_stocks = []
if sent:
scope_stocks = sent.get_scope_stocks()
if not scope_stocks and dm:
scope_stocks = list(dm.get_stock_list().index[:100])
print(" 范围: {} 只股票".format(len(scope_stocks)))
# 分类:已缓存(增量更新),未缓存(统计跳过)
cached = [c for c in scope_stocks if get_latest_trade_date(c)]
uncached = len(scope_stocks) - len(cached)
print(" 已缓存: {} 只 (增量更新), 未缓存: {} 只 (跳过,需首次批量预热)".format(len(cached), uncached))
synced = 0
for i, ts_code in enumerate(cached):
try:
n = dm.sync_daily(ts_code)
synced += n
except Exception:
continue
if (i + 1) % 100 == 0:
print(" [同步] 进度: {}/{}".format(i + 1, len(cached)))
print(" 同步完成: {} 条新数据 (已缓存{}/全量{})".format(synced, len(cached), len(scope_stocks)))
if uncached > 0:
print(" 提示: {} 只股票未缓存,运行 'agent_cli.py warmup' 首次批量预热".format(uncached))
# Step 2: 风险评估
print("\n[Step 2/4] 风险评估...")
risk = self.agents["risk"].execute()
results["risk"] = risk
print(f" 风险: {risk['risk_level']}, 仓位: {risk['target_exposure']:.0%}")
# Step 3: 选股打分
print("\n[Step 3/4] 股票打分...")
selection = self.agents["selection"].execute(date=date, top_n=15)
results["selection"] = selection
top = selection.get("top_picks", [])
if top:
print(" Top 5: {}".format(", ".join(p['ts_code'] for p in top[:5])))
# Step 4: 情绪因子
print("\n[Step 4/5] 情绪因子...")
sentiment_df = None
sent_eng = self.engines.get("sent")
if sent_eng:
try:
# 用范围内第一只有缓存的股票计算情绪因子
ref_code = cached[0] if cached else "000001.SZ"
sentiment_df = sent_eng.compute(ref_code, max_news=30)
if sentiment_df is not None and not sentiment_df.empty:
valid = sentiment_df.dropna(how="all")
print(" {}: {} 个因子, {} 个有效交易日".format(ref_code, sentiment_df.shape[1], len(valid)))
else:
print(" (无有效情绪数据)")
except Exception as e:
print(" [SKIP] 情绪因子计算失败: {}".format(e))
else:
print(" (SentimentEngine 未配置)")
results["sentiment"] = sentiment_df
# Step 5: 生成日报
print("\n[Step 5/5] 生成日报...")
report = self.agents["report"].execute(
date=date,
selection_result=selection,
risk_result=risk,
sentiment_result=sentiment_df,
)
results["report"] = report
print(f" 日报: {report['report_path']}")
self._last_results = results
print(f"\n{'='*60}")
print(f"[Orchestrator] 每日流程完成")
print(f"{'='*60}\n")
return results
# ── 研究流程(每周一次) ────────────────────────────────
def run_research_cycle(self, ts_codes: list[str] | None = None) -> dict:
"""
研究周期:
1. 因子发现与评估
2. 更新 IC 权重
"""
print(f"\n{'='*60}")
print(f"[Orchestrator] 研究周期")
print(f"{'='*60}")
print("\n[Step 1/2] 因子发现...")
research = self.agents["research"].execute(ts_codes=ts_codes)
results = {"research": research}
top = research.get("top_factors", [])
if top:
print(f" Top 5 因子:")
for f in top[:5]:
print(f" {f['name']:20s} IC={f['ic_mean']:+.4f} ICIR={f['icir']:.3f}")
return results
# ── 便捷方法 ──────────────────────────────────────────
def picks(self, date: str | None = None, top_n: int = 15) -> dict:
"""快速选股。"""
return self.agents["selection"].execute(date=date, top_n=top_n)
def risk_check(self) -> dict:
"""快速风险评估。"""
return self.agents["risk"].execute()
def generate_report(self, date: str | None = None) -> dict:
"""快速生成日报。"""
date = date or datetime.now().strftime("%Y%m%d")
# 尝试拉取最新数据(Tushare 优先,几秒即可完成)
dm = self.engines.get("dm")
if dm:
try:
dm.sync_daily("000001.SZ")
except Exception:
pass
# 查 DB 最新交易日
data_freshness = None
try:
from database.dao import get_latest_trade_date
data_freshness = get_latest_trade_date("000001.SZ")
except Exception:
pass
sel = self.picks(date)
risk = self.risk_check()
# 尝试取情绪因子
sentiment_df = None
sent_eng = self.engines.get("sent")
if sent_eng:
try:
sentiment_df = sent_eng.compute("000001.SZ", max_news=30)
except Exception:
pass
return self.agents["report"].execute(
date=date, selection_result=sel, risk_result=risk,
sentiment_result=sentiment_df, data_freshness=data_freshness,
)
@property
def last_results(self) -> dict:
return self._last_results