Files
myquant/finance/agents/risk_agent.py
T
Simon 73d191b43a feat: 量化引擎加固 — 新增测试 + 数据/因子/回测层优化
- 新增 finance/tests/ 6 个测试套件(agents/backtest/dao_upsert/factors/features/fundamental_lookahead)
- 数据层: data_manager / dao 优化,新增 upsert 逻辑
- 因子层: 基本面因子抽象定位 _mapping、ROE/PE/PB 重构
- 回测层: vectorbt/engine 大改动(251 行),report 增强
- ML 层: features/backtest_integration 特征工程与回测优化
- CLI: agent_cli 重构
- config/settings 扩充配置项
2026-08-31 14:01:06 +08:00

112 lines
3.8 KiB
Python

"""
RiskAgent — 仓位控制与风险预警。
根据市场波动率、回撤、相关性输出仓位建议和止损线。
"""
import numpy as np
import pandas as pd
from agents.base import BaseAgent
from config.settings import (
RISK_THRESHOLDS, RISK_EXPOSURE, RISK_MAX_SINGLE, RISK_STOP_LOSS,
)
class RiskAgent(BaseAgent):
"""仓位控制 Agent。"""
name = "Risk"
description = "仓位控制与风险预警"
# 风险等级阈值(来自配置中心)
THRESHOLDS = RISK_THRESHOLDS
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"
elif market_vol > self.THRESHOLDS["medium"]["vol"] or current_dd < self.THRESHOLDS["medium"]["dd"]:
risk_level = "medium"
else:
risk_level = "low"
target_exposure = RISK_EXPOSURE[risk_level]
# 最近 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 = RISK_MAX_SINGLE[risk_level]
stop_loss = RISK_STOP_LOSS[risk_level]
# 持仓预警
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": ["数据不足,使用默认风险参数"],
}