Files
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

114 lines
3.8 KiB
Python

"""
RiskAgent — 仓位控制与风险预警。
根据市场波动率、回撤、相关性输出仓位建议和止损线。
"""
import numpy as np
import pandas as pd
from agents.base import BaseAgent
class RiskAgent(BaseAgent):
"""仓位控制 Agent。"""
name = "Risk"
description = "仓位控制与风险预警"
# 风险等级阈值
THRESHOLDS = {
"high": {"vol": 35, "dd": -15},
"medium": {"vol": 25, "dd": -8},
}
def execute(
self,
holdings: dict[str, float] | None = None,
market_index: str = "000001.SH", # 上证指数,非个股
ts_codes: list[str] | None = None,
) -> dict:
"""
评估市场风险并输出仓位建议。
参数:
holdings: {ts_code: 持仓比例}
market_index: 市场参考标的
ts_codes: 持仓股票列表
返回:
{"risk_level": str, "target_exposure": float, "indicators": dict, "alerts": list}
"""
holdings = holdings or {}
price = self.dm.get_daily(market_index)
if price is None or price.empty:
return self._default_result()
price = price.set_index("trade_date").sort_index()
close = price["close"]
daily_ret = close.pct_change().dropna()
# 市场波动率(年化)
market_vol = float(daily_ret.tail(252).std() * np.sqrt(252) * 100) if len(daily_ret) >= 20 else 30
# 当前回撤
peak = close.expanding().max()
current_dd = float((close.iloc[-1] / peak.iloc[-1] - 1) * 100)
# 风险等级
if market_vol > self.THRESHOLDS["high"]["vol"] or current_dd < self.THRESHOLDS["high"]["dd"]:
risk_level = "high"
target_exposure = 0.30
elif market_vol > self.THRESHOLDS["medium"]["vol"] or current_dd < self.THRESHOLDS["medium"]["dd"]:
risk_level = "medium"
target_exposure = 0.60
else:
risk_level = "low"
target_exposure = 0.85
# 最近 N 日涨跌
ret_5d = float(close.pct_change(5).iloc[-1] * 100) if len(close) >= 6 else 0
ret_20d = float(close.pct_change(20).iloc[-1] * 100) if len(close) >= 21 else 0
# 单票上限(风险越高越集中)
max_single = 0.15 if risk_level == "low" else (0.10 if risk_level == "medium" else 0.05)
stop_loss = -0.05 if risk_level == "low" else (-0.08 if risk_level == "medium" else -0.12)
# 持仓预警
alerts = []
if current_dd < -10:
alerts.append(f"市场回撤 {current_dd:.1f}%,考虑减仓")
if market_vol > 30:
alerts.append(f"市场波动率 {market_vol:.1f}%,处于高位")
for code, pct in holdings.items():
if pct > max_single:
alerts.append(f"{code} 仓位 {pct:.0%} 超过上限 {max_single:.0%}")
self.log(f"风险={risk_level} 波动={market_vol:.1f}% 回撤={current_dd:.1f}% 仓位→{target_exposure:.0%}")
return {
"risk_level": risk_level,
"target_exposure": round(target_exposure, 2),
"max_single_position": round(max_single, 2),
"stop_loss": round(stop_loss, 2),
"indicators": {
"market_volatility": round(market_vol, 1),
"current_drawdown": round(current_dd, 1),
"return_5d": round(ret_5d, 1),
"return_20d": round(ret_20d, 1),
"close": round(float(close.iloc[-1]), 2),
},
"alerts": alerts,
}
@staticmethod
def _default_result() -> dict:
return {
"risk_level": "medium",
"target_exposure": 0.60,
"max_single_position": 0.10,
"stop_loss": -0.08,
"indicators": {},
"alerts": ["数据不足,使用默认风险参数"],
}