401 lines
14 KiB
Python
401 lines
14 KiB
Python
"""
|
|
风控预警和执行系统
|
|
功能:
|
|
1. 三级预警机制(黄/橙/红)
|
|
2. 预警触发后的执行规则
|
|
3. 三级风控体系(个股→板块→整体)
|
|
4. 执行记录留存,方便回测复盘
|
|
|
|
Author: 关羽(云长)
|
|
Date: 2026-03-27
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import List, Dict, Optional, Tuple, Callable
|
|
from enum import Enum
|
|
from datetime import datetime
|
|
import json
|
|
|
|
|
|
class WarningLevel(Enum):
|
|
YELLOW = 1 # 黄色预警:观察
|
|
ORANGE = 2 # 橙色预警:减仓
|
|
RED = 3 # 红色预警:清仓
|
|
|
|
|
|
@dataclass
|
|
class WarningMessage:
|
|
"""预警消息"""
|
|
level: WarningLevel
|
|
scope: str # individual / sector / portfolio
|
|
target: str # code / sector name
|
|
level_name: str
|
|
reason: str
|
|
timestamp: str
|
|
current_position: float # 当前仓位
|
|
target_position: float # 目标仓位
|
|
executed: bool = False
|
|
|
|
|
|
@dataclass
|
|
class RiskWarningConfig:
|
|
"""风控预警参数配置"""
|
|
# 个股预警阈值
|
|
yellow_threshold: float = 0.2 # 风险分0.2→黄
|
|
orange_threshold: float = 0.4 # 风险分0.4→橙
|
|
red_threshold: float = 0.7 # 风险分0.7→红
|
|
|
|
# 执行规则:仓位调整比例
|
|
yellow_reduce: float = 0.0 # 黄色不减仓
|
|
orange_reduce: float = 0.5 # 橙色减仓一半
|
|
red_reduce: float = 1.0 # 红色清仓
|
|
|
|
# 板块预警阈值
|
|
sector_warning_threshold: int = 10 # 风险积分10→黄
|
|
sector_orange_threshold: int = 25 # 积分25→橙
|
|
sector_red_threshold: int = 40 # 积分40→红
|
|
|
|
# 组合预警阈值
|
|
portfolio_yellow: float = 0.2 # 总分0.2→黄
|
|
portfolio_orange: float = 0.4 # 总分0.4→橙
|
|
portfolio_red: float = 0.7 # 总分0.7→红
|
|
|
|
|
|
default_config = RiskWarningConfig()
|
|
|
|
|
|
class IndividualRiskWarning:
|
|
"""个股风险预警"""
|
|
|
|
def __init__(self, config: RiskWarningConfig = None):
|
|
self.config = config or default_config
|
|
|
|
def evaluate(self, risk_score: float) -> WarningLevel:
|
|
"""根据风险分评估预警等级"""
|
|
if risk_score >= self.config.red_threshold:
|
|
return WarningLevel.RED
|
|
elif risk_score >= self.config.orange_threshold:
|
|
return WarningLevel.ORANGE
|
|
elif risk_score >= self.config.yellow_threshold:
|
|
return WarningLevel.YELLOW
|
|
else:
|
|
return None # 无预警
|
|
|
|
def calculate_target_position(self,
|
|
current_position: float,
|
|
level: WarningLevel) -> float:
|
|
"""计算目标仓位"""
|
|
if level == WarningLevel.RED:
|
|
return current_position * (1 - self.config.red_reduce)
|
|
elif level == WarningLevel.ORANGE:
|
|
return current_position * (1 - self.config.orange_reduce)
|
|
elif level == WarningLevel.YELLOW:
|
|
return current_position * (1 - self.config.yellow_reduce)
|
|
else:
|
|
return current_position
|
|
|
|
|
|
class SectorRiskWarning:
|
|
"""板块风险预警"""
|
|
|
|
def __init__(self, config: RiskWarningConfig = None):
|
|
self.config = config or default_config
|
|
|
|
def evaluate(self, risk_score: int) -> WarningLevel:
|
|
if risk_score >= self.config.sector_red_threshold:
|
|
return WarningLevel.RED
|
|
elif risk_score >= self.config.sector_orange_threshold:
|
|
return WarningLevel.ORANGE
|
|
elif risk_score >= self.config.sector_warning_threshold:
|
|
return WarningLevel.YELLOW
|
|
else:
|
|
return None
|
|
|
|
def get_target_ratio(self, current_ratio: float, level: WarningLevel) -> float:
|
|
"""计算目标仓位比例"""
|
|
if level == WarningLevel.RED:
|
|
return current_ratio * 0.25 # 保留25%
|
|
elif level == WarningLevel.ORANGE:
|
|
return current_ratio * 0.5 # 保留一半
|
|
elif level == WarningLevel.YELLOW:
|
|
return current_ratio # 不变
|
|
else:
|
|
return current_ratio
|
|
|
|
|
|
class PortfolioRiskWarning:
|
|
"""整体组合风险预警"""
|
|
|
|
def __init__(self, config: RiskWarningConfig = None):
|
|
self.config = config or default_config
|
|
|
|
def evaluate(self, total_risk_score: float) -> WarningLevel:
|
|
if total_risk_score >= self.config.portfolio_red:
|
|
return WarningLevel.RED
|
|
elif total_risk_score >= self.config.portfolio_orange:
|
|
return WarningLevel.ORANGE
|
|
elif total_risk_score >= self.config.portfolio_yellow:
|
|
return WarningLevel.YELLOW
|
|
else:
|
|
return None
|
|
|
|
def get_overall_target_ratio(self, level: WarningLevel) -> float:
|
|
"""整体目标仓位比例"""
|
|
if level == WarningLevel.RED:
|
|
return 0.25 # 保留25%
|
|
elif level == WarningLevel.ORANGE:
|
|
return 0.5 # 保留一半
|
|
elif level == WarningLevel.YELLOW:
|
|
return 0.8 # 保留80%
|
|
else:
|
|
return 1.0 # 满仓
|
|
|
|
|
|
class RiskWarningSystem:
|
|
"""三级风控预警总系统(个股→板块→整体)"""
|
|
|
|
def __init__(self, config: RiskWarningConfig = None):
|
|
self.config = config or default_config
|
|
self.individual_warn = IndividualRiskWarning(config)
|
|
self.sector_warn = SectorRiskWarning(config)
|
|
self.portfolio_warn = PortfolioRiskWarning(config)
|
|
self.warning_history: List[WarningMessage] = []
|
|
self.execution_callback: Optional[Callable] = None
|
|
|
|
def register_execution_callback(self, callback: Callable):
|
|
"""注册执行回调,预警触发后自动调用"""
|
|
self.execution_callback = callback
|
|
|
|
def evaluate_stock(self,
|
|
code: str,
|
|
name: str,
|
|
risk_score: float,
|
|
current_position: float) -> Optional[WarningMessage]:
|
|
"""评估个股风险,产生预警"""
|
|
level = self.individual_warn.evaluate(risk_score)
|
|
if level is None:
|
|
return None
|
|
|
|
target = self.individual_warn.calculate_target_position(current_position, level)
|
|
level_names = {
|
|
WarningLevel.YELLOW: "黄色",
|
|
WarningLevel.ORANGE: "橙色",
|
|
WarningLevel.RED: "红色"
|
|
}
|
|
|
|
msg = WarningMessage(
|
|
level=level,
|
|
scope="individual",
|
|
target=f"{code} {name}",
|
|
level_name=level_names[level],
|
|
reason=f"个股风险分{risk_score:.2f},触发{level_names[level]}预警",
|
|
timestamp=datetime.now().isoformat(),
|
|
current_position=current_position,
|
|
target_position=target
|
|
)
|
|
|
|
self.warning_history.append(msg)
|
|
return msg
|
|
|
|
def evaluate_sector(self,
|
|
name: str,
|
|
risk_score: int,
|
|
current_ratio: float) -> Optional[WarningMessage]:
|
|
"""评估板块风险,产生预警"""
|
|
level = self.sector_warn.evaluate(risk_score)
|
|
if level is None:
|
|
return None
|
|
|
|
target = self.sector_warn.get_target_ratio(current_ratio, level)
|
|
level_names = {
|
|
WarningLevel.YELLOW: "黄色",
|
|
WarningLevel.ORANGE: "橙色",
|
|
WarningLevel.RED: "红色"
|
|
}
|
|
|
|
msg = WarningMessage(
|
|
level=level,
|
|
scope="sector",
|
|
target=name,
|
|
level_name=level_names[level],
|
|
reason=f"板块风险积分{risk_score},触发{level_names[level]}预警",
|
|
timestamp=datetime.now().isoformat(),
|
|
current_position=current_ratio,
|
|
target_position=target
|
|
)
|
|
|
|
self.warning_history.append(msg)
|
|
return msg
|
|
|
|
def evaluate_portfolio(self, total_risk_score: float) -> Optional[WarningMessage]:
|
|
"""评估整体组合风险"""
|
|
level = self.portfolio_warn.evaluate(total_risk_score)
|
|
if level is None:
|
|
return None
|
|
|
|
target_ratio = self.portfolio_warn.get_overall_target_ratio(level)
|
|
level_names = {
|
|
WarningLevel.YELLOW: "黄色",
|
|
WarningLevel.ORANGE: "橙色",
|
|
WarningLevel.RED: "红色"
|
|
}
|
|
|
|
msg = WarningMessage(
|
|
level=level,
|
|
scope="portfolio",
|
|
target="整体组合",
|
|
level_name=level_names[level],
|
|
reason=f"组合总风险分{total_risk_score:.2f},触发{level_names[level]}预警",
|
|
timestamp=datetime.now().isoformat(),
|
|
current_position=1.0, # 当前总仓位比例
|
|
target_position=target_ratio
|
|
)
|
|
|
|
self.warning_history.append(msg)
|
|
return msg
|
|
|
|
def get_pending_warnings(self) -> List[WarningMessage]:
|
|
"""获取未执行预警"""
|
|
return [w for w in self.warning_history if not w.executed]
|
|
|
|
def mark_executed(self, warning: WarningMessage):
|
|
"""标记已执行"""
|
|
warning.executed = True
|
|
|
|
def get_warning_report(self) -> str:
|
|
"""生成预警报告"""
|
|
pending = self.get_pending_warnings()
|
|
all_warnings = self.warning_history
|
|
|
|
levels = {
|
|
WarningLevel.YELLOW: "🟡 黄色",
|
|
WarningLevel.ORANGE: "🟠 橙色",
|
|
WarningLevel.RED: "🔴 红色",
|
|
}
|
|
|
|
lines = []
|
|
lines.append("=" * 60)
|
|
lines.append("风控预警系统报告")
|
|
lines.append("=" * 60)
|
|
lines.append(f"总预警数量: {len(all_warnings)}")
|
|
lines.append(f"未执行预警: {len(pending)}")
|
|
lines.append("")
|
|
|
|
if pending:
|
|
lines.append("⚠️ 待执行预警:")
|
|
for w in pending:
|
|
lines.append(f" [{levels[w.level]}] [{w.scope}] {w.target}")
|
|
lines.append(f" 原因: {w.reason}")
|
|
lines.append(f" 当前仓位: {w.current_position:.2%} → 目标仓位: {w.target_position:.2%}")
|
|
lines.append("")
|
|
else:
|
|
lines.append("✅ 无待执行预警")
|
|
|
|
lines.append("=" * 60)
|
|
return "\n".join(lines)
|
|
|
|
def export_history(self, path: str):
|
|
"""导出预警历史到json,方便复盘"""
|
|
data = []
|
|
for w in self.warning_history:
|
|
data.append({
|
|
"level": w.level.value,
|
|
"scope": w.scope,
|
|
"target": w.target,
|
|
"level_name": w.level_name,
|
|
"reason": w.reason,
|
|
"timestamp": w.timestamp,
|
|
"current_position": w.current_position,
|
|
"target_position": w.target_position,
|
|
"executed": w.executed
|
|
})
|
|
|
|
with open(path, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
|
|
class RiskExecution:
|
|
"""风控执行器,根据预警执行调仓"""
|
|
|
|
def __init__(self, warning_system: RiskWarningSystem):
|
|
self.warning_system = warning_system
|
|
|
|
def execute_warnings(self) -> List[dict]:
|
|
"""执行所有待执行预警"""
|
|
pending = self.warning_system.get_pending_warnings()
|
|
execution_records = []
|
|
|
|
for warning in pending:
|
|
record = {
|
|
"scope": warning.scope,
|
|
"target": warning.target,
|
|
"level": warning.level_name,
|
|
"current": warning.current_position,
|
|
"target": warning.target_position,
|
|
"adjust_amount": warning.current_position - warning.target_position,
|
|
"timestamp": warning.timestamp
|
|
}
|
|
execution_records.append(record)
|
|
self.warning_system.mark_executed(warning)
|
|
|
|
return execution_records
|
|
|
|
def get_execution_report(self, records: List[dict]) -> str:
|
|
"""生成执行报告"""
|
|
lines = []
|
|
lines.append("=" * 60)
|
|
lines.append("风控执行报告")
|
|
lines.append("=" * 60)
|
|
lines.append(f"本次执行 {len(records)} 条预警")
|
|
lines.append("")
|
|
|
|
for r in records:
|
|
lines.append(f"⚠️ [{r['level']}] {r['scope']} {r['target']}:")
|
|
lines.append(f" 仓位调整: {r['current']:.2%} → {r['target']:.2%},需要卖出{r['adjust_amount']:.2%}")
|
|
|
|
if not records:
|
|
lines.append("✅ 无需要执行的调整")
|
|
|
|
lines.append("=" * 60)
|
|
return "\n".join(lines)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=== 测试风控预警和执行系统 ===\n")
|
|
|
|
system = RiskWarningSystem(default_config)
|
|
|
|
# 测试个股预警
|
|
msg1 = system.evaluate_stock("600000", "浦发银行", 0.45, 100000)
|
|
msg2 = system.evaluate_stock("000001", "平安银行", 0.75, 150000)
|
|
msg3 = system.evaluate_stock("002XXX", "AI龙头", 0.15, 80000)
|
|
|
|
print("个股预警测试:")
|
|
for msg in [msg1, msg2, msg3]:
|
|
if msg:
|
|
print(f" {msg.target}: {msg.level_name} → 当前{msg.current_position:.0f} → 目标{msg.target_position:.0f}")
|
|
print()
|
|
|
|
# 测试板块预警
|
|
s_msg1 = system.evaluate_sector("AI", 30, 0.22)
|
|
s_msg2 = system.evaluate_sector("新能源", 15, 0.10)
|
|
print("板块预警测试:")
|
|
for msg in [s_msg1, s_msg2]:
|
|
if msg:
|
|
print(f" {msg.target}: {msg.level_name} → 当前{msg.current_position:.1%} → 目标{msg.target_position:.1%}")
|
|
print()
|
|
|
|
# 测试组合预警
|
|
p_msg = system.evaluate_portfolio(0.45)
|
|
print(f"组合预警: {p_msg.level_name if p_msg else 'None'}")
|
|
print()
|
|
|
|
# 生成报告
|
|
print(system.get_warning_report())
|
|
print()
|
|
|
|
# 执行预警
|
|
executor = RiskExecution(system)
|
|
records = executor.execute_warnings()
|
|
print(executor.get_execution_report(records))
|