242 lines
9.2 KiB
Python
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)}")
|