Files
sanguo_quant_live/guanyu-risk/common/structural_market_risk.py
T

242 lines
9.2 KiB
Python

"""
结构化行情择时风控模块
专门处理A股结构化行情下的板块集中度风险和择时信号
功能:
1. 择时:单板块连续大涨提醒风险,连续大跌提示低吸
2. 风控:控制板块和风格集中度,不押注单一方向
3. 消息风险:热点板块灵敏度提高,提前预警利好出尽
Author: 关羽(云长)
Date: 2026-03-27
"""
from dataclasses import dataclass
from typing import List, Dict, Tuple
@dataclass
class SectorInfo:
"""板块信息"""
name: str
style: str # 风格:AI/新能源/消费/周期等
recent_gain: float # 近期累计涨幅%
position_ratio: float # 组合中该板块仓位比例
stock_count: int # 持有股票数量
is_hot: bool = False # 是否是当前热点板块
class StructuredMarketTiming:
"""结构化行情择时"""
def __init__(self,
high_position_threshold: float = 0.15, # 单板块仓位超15%提示减仓
sector_rally_warning: float = 30.0, # 累计涨30%提示风险
sector_rally_stop: float = 50.0, # 累计涨50%强制提高风控权重
sector_dump_opportunity: float = -20.0): # 跌20%提示机会
self.high_position_threshold = high_position_threshold
self.sector_rally_warning = sector_rally_warning
self.sector_rally_stop = sector_rally_stop
self.sector_dump_opportunity = sector_dump_opportunity
def timing_sector(self, sector: SectorInfo) -> Tuple[str, str, int]:
"""
板块择时
返回:(信号类型, 建议, 风险增量评分)
信号类型: bull/bear/neutral
"""
risk_increment = 0
suggestion = ""
signal = "neutral"
# 单板块仓位过高提醒
if sector.position_ratio > self.high_position_threshold:
risk_increment += int((sector.position_ratio - self.high_position_threshold) * 100)
suggestion += f"\n⚠️ 单板块仓位{sector.position_ratio:.1%},超过{self.high_position_threshold:.1%}阈值,建议适度减仓分散"
signal = "bear"
# 板块连续大涨提醒风险
if sector.recent_gain >= self.sector_rally_stop:
risk_increment += 20
suggestion += f"\n🔴 板块累计涨幅{sector.recent_gain:.1f}%,超过{self.sector_rally_stop:.0f}%,警惕过热,建议整体止盈"
signal = "bear"
elif sector.recent_gain >= self.sector_rally_warning:
risk_increment += 10
suggestion += f"\n🟡 板块累计涨幅{sector.recent_gain:.1f}%,已有较大涨幅,提高风控警惕"
signal = "bear"
# 板块连续大跌提示机会
if sector.recent_gain <= self.sector_dump_opportunity and sector.recent_gain >= -40:
suggestion += f"\n✅ 板块累计跌幅{-sector.recent_gain:.1f}%,如果基本面没问题,可考虑适度低吸布局"
signal = "bull"
if not suggestion:
suggestion = "✅ 板块仓位和涨幅正常,无特殊风险"
signal = "neutral"
return signal, suggestion.strip(), risk_increment
class SectorConcentrationRisk:
"""板块和风格集中度风控"""
def __init__(self,
max_single_sector: float = 0.25, # 单板块最大仓位25%
max_single_style: float = 0.40, # 单风格最大仓位40%
max_hot_sectors_total: float = 0.50): # 所有热点板块合计最大50%
self.max_single_sector = max_single_sector
self.max_single_style = max_single_style
self.max_hot_sectors_total = max_hot_sectors_total
def check_concentration(self, sectors: List[SectorInfo]) -> Tuple[int, List[str]]:
"""检查集中度风险,返回总风险增量和警告列表"""
risk_increment = 0
warnings = []
# 1. 单板块检查
for sector in sectors:
if sector.position_ratio > self.max_single_sector:
extra = (sector.position_ratio - self.max_single_sector) * 100
risk_increment += int(extra)
warnings.append(f"⚠️ 板块【{sector.name}】仓位{sector.position_ratio:.1%},超过最大限制{self.max_single_sector:.1%}")
# 2. 按风格汇总检查
style_summary: Dict[str, float] = {}
for sector in sectors:
if sector.style not in style_summary:
style_summary[sector.style] = 0.0
style_summary[sector.style] += sector.position_ratio
for style, ratio in style_summary.items():
if ratio > self.max_single_style:
extra = (ratio - self.max_single_style) * 50
risk_increment += int(extra)
warnings.append(f"⚠️ 风格【{style}】总仓位{ratio:.1%},超过最大限制{self.max_single_style:.1%},建议分散")
# 3. 热点板块合计检查
hot_total = sum(s.position_ratio for s in sectors if s.is_hot)
if hot_total > self.max_hot_sectors_total:
extra = (hot_total - self.max_hot_sectors_total) * 80
risk_increment += int(extra)
warnings.append(f"⚠️ 所有热点板块合计仓位{hot_total:.1%},超过{self.max_hot_sectors_total:.1%},总体过热风险")
return risk_increment, warnings
class HotSectorNewsRiskAdjust:
"""热点板块消息风险调整,热点更容易利好出尽,灵敏度提高"""
def __init__(self,
hot_risk_multiplier: float = 2.0, # 热点板块风险分放大倍数
medium_risk_multiplier: float = 1.5): # 中度上涨板块放大倍数
self.hot_risk_multiplier = hot_risk_multiplier
self.medium_risk_multiplier = medium_risk_multiplier
def adjust_risk_score(self, base_score: int, sector_recent_gain: float) -> int:
"""根据板块涨幅调整风险分"""
if sector_recent_gain >= 50:
return int(base_score * self.hot_risk_multiplier)
elif sector_recent_gain >= 30:
return int(base_score * self.medium_risk_multiplier)
else:
return base_score
def get_structural_risk_report(sectors: List[SectorInfo],
timing: StructuredMarketTiming,
concentration: SectorConcentrationRisk) -> str:
"""生成结构化行情风险报告"""
timing = StructuredMarketTiming()
concentration = SectorConcentrationRisk()
total_risk = 0
all_warnings = []
all_suggestions = []
# 逐个板块择时
for sector in sectors:
signal, suggestion, risk_inc = timing.timing_sector(sector)
total_risk += risk_inc
if signal != "neutral":
all_suggestions.append(f"{sector.name}{suggestion}")
# 集中度检查
risk_inc, warnings = concentration.check_concentration(sectors)
total_risk += risk_inc
all_warnings.extend(warnings)
# 生成报告
lines = []
lines.append("=" * 60)
lines.append("结构化行情板块风控择时报告")
lines.append("=" * 60)
lines.append(f"监控板块数量: {len(sectors)}")
lines.append(f"总风险增量评分: {total_risk}")
lines.append("")
if all_warnings:
lines.append("⚠️ 集中度风险警告:")
for w in all_warnings:
lines.append(f"{w}")
lines.append("")
if all_suggestions:
lines.append("💡 择时建议:")
for s in all_suggestions:
lines.append(f"{s}")
lines.append("")
if total_risk >= 30:
lines.append("⚠️ 整体结论:板块风险较高,建议减仓分散")
elif total_risk >= 10:
lines.append("🟡 整体结论:存在一定板块风险,建议密切观察")
else:
lines.append("✅ 整体结论:板块结构正常,无明显风险")
lines.append("=" * 60)
return "\n".join(lines)
if __name__ == "__main__":
# 测试
print("=== 测试结构化行情风控 ===\n")
sectors = [
SectorInfo(
name="AI算力",
style="AI",
recent_gain=65.0,
position_ratio=0.22,
stock_count=4,
is_hot=True
),
SectorInfo(
name="新能源",
style="新能源",
recent_gain=-25.0,
position_ratio=0.10,
stock_count=2,
is_hot=False
),
SectorInfo(
name="消费",
style="消费",
recent_gain=8.0,
position_ratio=0.12,
stock_count=3,
is_hot=False
)
]
timing = StructuredMarketTiming()
concentration = SectorConcentrationRisk()
print(get_structural_risk_report(sectors, timing, concentration))
# 测试热点风险放大
adjust = HotSectorNewsRiskAdjust()
base_score = 10
print(f"\n基础风险分10,不同涨幅放大后:")
print(f" 涨幅10% → {adjust.adjust_risk_score(10, 10)}")
print(f" 涨幅35% → {adjust.adjust_risk_score(10, 35)}")
print(f" 涨幅60% → {adjust.adjust_risk_score(10, 60)}")