- 新增 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 扩充配置项
112 lines
3.8 KiB
Python
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": ["数据不足,使用默认风险参数"],
|
|
}
|