""" 结构化适配动态多因子 - 风控和择时模块 功能: 1. 个股风险评估:五维风险 + 消息提前监控 + 国际联动风险 2. 择时适配结构化:板块轮动择时,仓位控制,热度控制 3. 风控适配结构化:板块风格集中度控制,热点灵敏度提高 4. 全参数抽象化:所有阈值可配置,方便参数优化 Author: 关羽(云长) Date: 2026-03-27 """ from dataclasses import dataclass from typing import List, Dict, Optional, Tuple from enum import Enum class RiskLevel(Enum): SAFE = 0 WATCH = 1 REDUCE = 2 EXIT = 3 class Sentiment(Enum): STRONG_BULL = 2 BULL = 1 NEUTRAL = 0 BEAR = -1 STRONG_BEAR = -2 # ==================== 数据结构 ==================== @dataclass class IndividualStockRisk: """单个股风险输入数据""" code: str name: str # 五维基础风险 volatility: float # 波动率风险 0~1 fundamental: float # 基本面风险 0~1 liquidity: float # 流动性风险 0~1 valuation: float # 估值风险 0~1 # 消息监控维度 recent_vol_change: float # 成交量变化相对20日均值 recent_pct_change: float # 近5日涨跌幅% gap_down: bool = False finance_balance_change: float = 0.0 # 融资余额周变化 has_inst_sell: bool = False # 龙虎榜机构卖出 has_bulk_discount: bool = False bulk_discount: float = 0.0 discussion_change: float = 0.0 # 讨论量变化倍数 sentiment_score: float = 0.0 # -1~1 # 国际联动 is_ah: bool = False ah_hk_change: float = 0.0 is_commodity: bool = False commodity_future_change: float = 0.0 us_index_change: float = 0.0 has_major_intl_news: bool = False intl_news_sentiment: int = 0 # 结构化板块 sector_name: str = "" sector_style: str = "" sector_total_gain: float = 0.0 # 板块近期涨幅% sector_position_ratio: float = 0.0 # 板块仓位占比 @dataclass class SectorInfo: """板块信息""" name: str style: str recent_gain: float position_ratio: float stock_count: int is_hot: bool = False @dataclass class OverallRiskResult: """整体风险评估结果""" total_risk_score: float # 0~1 risk_level: RiskLevel individual_risk: float news_risk: float sector_risk: float suggestion: str warnings: List[str] # ==================== 参数配置 ==================== @dataclass class RiskConfig: """风控参数配置,全部抽象化,方便优化""" # === 个股消息监控参数 === vol_alert_threshold: float = 2.0 # 放量超过2倍预警 drop_alert_threshold: float = -7.0 # 5日跌幅超过7%预警 finance_decrease_threshold: float = -0.2 # 融资减少超过20%预警 bulk_discount_threshold: float = -0.08 # 大宗折价超过8%预警 discussion_hot_threshold: float = 5.0 # 讨论量超过5倍预警 # === 国际联动参数 === ah_drop_threshold: float = -3.0 # H股跌幅超过3%预警 commodity_drop_threshold: float = -4.0 # 商品跌幅超过4%预警 us_drop_threshold: float = -2.0 # 美股跌幅超过2%预警 # === 结构化择时参数 === single_sector_high_position: float = 0.15 # 单板块仓位超过提示减仓 sector_rally_warning: float = 30.0 # 板块涨幅30%提示 sector_rally_risk: float = 50.0 # 板块涨幅50%提高风险权重 sector_dump_opportunity: float = -20.0 # 板块跌幅20%提示机会 # === 结构化风控参数 === max_single_sector: float = 0.25 # 单板块最大仓位 max_single_style: float = 0.40 # 单风格最大仓位 max_hot_total: float = 0.50 # 热点板块总仓位最大 hot_risk_multiplier: float = 2.0 # 热点板块风险放大倍数 medium_hot_multiplier: float = 1.5 # 中等热点放大倍数 # === 五维风险权重 === weight_volatility: float = 0.15 weight_fundamental: float = 0.20 weight_liquidity: float = 0.15 weight_valuation: float = 0.20 weight_news: float = 0.30 # 消息风险权重更高 # === 风险等级阈值 === threshold_exit: float = 0.7 threshold_reduce: float = 0.4 threshold_watch: float = 0.2 # 默认配置 default_config = RiskConfig() # ==================== 个股风险评估 ==================== class IndividualRiskAssessor: """个股五维风险+消息风险评估""" def __init__(self, config: RiskConfig = None): self.config = config or default_config def assess_base_five_dimension(self, stock: IndividualStockRisk) -> float: """计算基础五维风险""" base_risk = ( stock.volatility * self.config.weight_volatility + stock.fundamental * self.config.weight_fundamental + stock.liquidity * self.config.weight_liquidity + stock.valuation * self.config.weight_valuation ) return base_risk def assess_news_risk(self, stock: IndividualStockRisk) -> Tuple[float, List[str]]: """计算消息面风险,返回风险分0~1和警告列表""" risk_points = 0 max_points = 100 warnings = [] # 1. 量价异常 if stock.recent_vol_change >= self.config.vol_alert_threshold and stock.recent_pct_change >= 5: risk_points += 15 warnings.append(f"近5日放量{stock.recent_vol_change:.1f}倍涨幅{stock.recent_pct_change:.1f}%,警惕利好出货") if stock.recent_pct_change <= self.config.drop_alert_threshold and stock.recent_vol_change >= 0.5: risk_points += 20 warnings.append(f"近5日放量下跌{stock.recent_pct_change:.1f}%,警惕利空提前泄露") if stock.gap_down: risk_points += 10 warnings.append("向下跳空缺口未回补,技术形态偏空") # 2. 融资变化 if stock.finance_balance_change <= self.config.finance_decrease_threshold: risk_points += 15 warnings.append(f"融资余额一周减少{stock.finance_balance_change:.1%},杠杆资金出逃") # 3. 机构买卖 if stock.has_inst_sell: risk_points += 20 warnings.append("龙虎榜机构大额卖出") if stock.has_bulk_discount and stock.bulk_discount <= self.config.bulk_discount_threshold: risk_points += 15 warnings.append(f"大宗交易折价{stock.bulk_discount:.1%},大股东出货") # 4. 舆情 if stock.discussion_change >= self.config.discussion_hot_threshold: risk_points += 10 warnings.append(f"讨论量暴涨{stock.discussion_change:.1f}倍,热度异常") if stock.sentiment_score <= -0.5: risk_points += 10 warnings.append(f"舆情偏空,情感分{stock.sentiment_score:.2f}") # 5. 国际联动 if stock.is_ah and stock.ah_hk_change <= self.config.ah_drop_threshold: risk_points += 15 warnings.append(f"A+H股,H股隔夜大跌{stock.ah_hk_change:.1f}%") if stock.is_commodity and stock.commodity_future_change <= self.config.commodity_drop_threshold: risk_points += 15 warnings.append(f"大宗商品股,对应期货大跌{stock.commodity_future_change:.1f}%") if stock.us_index_change <= self.config.us_drop_threshold: risk_points += 10 warnings.append(f"美股隔夜大跌{stock.us_index_change:.1f}%,A股承压") if stock.has_major_intl_news and stock.intl_news_sentiment == -1: risk_points += 20 warnings.append("重大国际利空消息,系统性风险上升") # 6. 结构化板块:热点放大风险 if stock.sector_total_gain >= self.config.sector_rally_risk: risk_points = int(risk_points * self.config.hot_risk_multiplier) warnings.append(f"板块累计涨幅{stock.sector_total_gain:.1f}%,已是热点,风险放大") elif stock.sector_total_gain >= self.config.sector_rally_warning: risk_points = int(risk_points * self.config.medium_hot_multiplier) warnings.append(f"板块累计涨幅{stock.sector_total_gain:.1f}%,中等热点,风险适度放大") risk_norm = min(risk_points / 50, 1.0) return risk_norm, warnings # ==================== 结构化板块择时风控 ==================== class StructuralSectorTiming: """结构化行情板块择时""" def __init__(self, config: RiskConfig = None): self.config = config or default_config def timing_single_sector(self, sector: SectorInfo) -> Tuple[str, str, int]: """单板块择时,返回信号、建议、风险增量""" risk_inc = 0 suggestion = "" signal = "neutral" # 仓位过高提醒 if sector.position_ratio > self.config.single_sector_high_position: extra = (sector.position_ratio - self.config.single_sector_high_position) * 100 risk_inc += int(extra) suggestion += f"\n⚠️ 单板块仓位{sector.position_ratio:.1%},超过{self.config.single_sector_high_position:.1%},建议减仓分散" signal = "bear" # 涨幅过大提醒风险 if sector.recent_gain >= self.config.sector_rally_risk: risk_inc += 20 suggestion += f"\n🔴 板块累计涨幅{sector.recent_gain:.1f}%,超过{self.config.sector_rally_risk:.0f}%,警惕过热,建议提高止盈标准" signal = "bear" elif sector.recent_gain >= self.config.sector_rally_warning: risk_inc += 10 suggestion += f"\n🟡 板块累计涨幅{sector.recent_gain:.1f}%,已有较大涨幅,提高风控警惕" signal = "bear" # 跌幅过大提示机会 if sector.recent_gain <= self.config.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_inc class SectorConcentrationControl: """板块和风格集中度风控""" def __init__(self, config: RiskConfig = None): self.config = config or default_config def check_concentration(self, sectors: List[SectorInfo]) -> Tuple[int, List[str]]: """检查集中度风险,返回风险增量和警告""" risk_inc = 0 warnings = [] # 单板块检查 for sector in sectors: if sector.position_ratio > self.config.max_single_sector: extra = (sector.position_ratio - self.config.max_single_sector) * 100 risk_inc += int(extra) warnings.append(f"⚠️ 板块【{sector.name}】仓位{sector.position_ratio:.1%},超过最大限制{self.config.max_single_sector:.1%}") # 单风格检查 style_summary: Dict[str, float] = {} for sector in sectors: style_summary[sector.style] = style_summary.get(sector.style, 0.0) + sector.position_ratio for style, ratio in style_summary.items(): if ratio > self.config.max_single_style: extra = (ratio - self.config.max_single_style) * 50 risk_inc += int(extra) warnings.append(f"⚠️ 风格【{style}】总仓位{ratio:.1%},超过最大限制{self.config.max_single_style:.1%},建议分散配置") # 热点总仓位检查 hot_total = sum(s.position_ratio for s in sectors if s.is_hot) if hot_total > self.config.max_hot_total: extra = (hot_total - self.config.max_hot_total) * 80 risk_inc += int(extra) warnings.append(f"⚠️ 所有热点板块合计仓位{hot_total:.1%},超过{self.config.max_hot_total:.1%},总体过热风险") return risk_inc, warnings # ==================== 总风控控制器 ==================== class StructuredDynamicFactorsRiskControl: """结构化动态多因子总风控控制器""" def __init__(self, config: RiskConfig = None): self.config = config or default_config self.individual_assessor = IndividualRiskAssessor(config) self.sector_timing = StructuralSectorTiming(config) self.concentration_ctrl = SectorConcentrationControl(config) def assess_stock(self, stock: IndividualStockRisk) -> dict: """评估单个股风险""" # 基础五维 base_risk = self.individual_assessor.assess_base_five_dimension(stock) # 消息风险 news_risk, news_warnings = self.individual_assessor.assess_news_risk(stock) # 综合计算 total_risk = ( base_risk * (1 - self.config.weight_news) + news_risk * self.config.weight_news ) # 风险等级 if total_risk >= self.config.threshold_exit: level = RiskLevel.EXIT suggestion = "建议清仓离场" elif total_risk >= self.config.threshold_reduce: level = RiskLevel.REDUCE suggestion = "建议减仓控制风险" elif total_risk >= self.config.threshold_watch: level = RiskLevel.WATCH suggestion = "继续观察,不新开仓" else: level = RiskLevel.SAFE suggestion = "安全,按计划操作" return { "code": stock.code, "name": stock.name, "total_risk": total_risk, "base_risk": base_risk, "news_risk": news_risk, "risk_level": level, "suggestion": suggestion, "warnings": news_warnings } def assess_portfolio(self, stocks: List[IndividualStockRisk], sectors: List[SectorInfo]) -> OverallRiskResult: """评估整个组合风险""" # 1. 个股平均风险 if stocks: total_individual = sum(self.assess_stock(s)['total_risk'] for s in stocks) avg_individual = total_individual / len(stocks) else: avg_individual = 0.0 # 2. 板块风险 sector_risk_inc, sector_warnings = self.concentration_ctrl.check_concentration(sectors) for sector in sectors: _, _, inc = self.sector_timing.timing_single_sector(sector) sector_risk_inc += inc sector_risk = min(sector_risk_inc / 50, 1.0) # 3. 消息风险平均 total_news = 0 all_warnings = [] for stock in stocks: nr, w = self.individual_assessor.assess_news_risk(stock) total_news += nr all_warnings.extend(w) avg_news = total_news / len(stocks) if stocks else 0.0 # 4. 整体综合风险 total_score = (avg_individual * 0.6) + (sector_risk * 0.4) total_score = min(total_score, 1.0) # 风险等级 if total_score >= self.config.threshold_exit: level = RiskLevel.EXIT suggestion = "整体风险高,建议清仓休息" elif total_score >= self.config.threshold_reduce: level = RiskLevel.REDUCE suggestion = "整体风险中等偏高,建议减仓" elif total_score >= self.config.threshold_watch: level = RiskLevel.WATCH suggestion = "存在一定风险,密切观察" else: level = RiskLevel.SAFE suggestion = "整体风险正常,按计划操作" all_warnings.extend(sector_warnings) return OverallRiskResult( total_risk_score=total_score, risk_level=level, individual_risk=avg_individual, news_risk=avg_news, sector_risk=sector_risk, suggestion=suggestion, warnings=all_warnings ) def get_risk_report(self, result: OverallRiskResult) -> str: """生成风控报告""" levels = { RiskLevel.SAFE: "🟢 安全", RiskLevel.WATCH: "🟡 关注", RiskLevel.REDUCE: "🟠 减仓", RiskLevel.EXIT: "🔴 离场", } lines = [] lines.append("=" * 60) lines.append("结构化动态多因子 组合风控报告") lines.append("=" * 60) lines.append(f"总体风险评分: {result.total_risk_score:.2f}") lines.append(f"风险等级: {levels[result.risk_level]}") lines.append(f"个股平均风险: {result.individual_risk:.2f}") lines.append(f"消息平均风险: {result.news_risk:.2f}") lines.append(f"板块结构风险: {result.sector_risk:.2f}") lines.append("") if result.warnings: lines.append("⚠️ 风险警告:") for w in result.warnings: lines.append(f" • {w}") lines.append("") lines.append(f"💡 操作建议: {result.suggestion}") lines.append("=" * 60) return "\n".join(lines) def get_sector_timing_report(self, sectors: List[SectorInfo]) -> str: """生成板块择时报告""" lines = [] lines.append("=" * 60) lines.append("结构化板块择时报告") lines.append("=" * 60) total_risk = 0 for sector in sectors: signal, suggestion, inc = self.sector_timing.timing_single_sector(sector) total_risk += inc lines.append(f"【{sector.name}】") lines.append(f" 仓位: {sector.position_ratio:.1%} 近期涨幅: {sector.recent_gain:.1f}%") lines.append(f" {suggestion}") lines.append("") lines.append(f"总风险增量: {total_risk}") 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") # 测试1: 单个股评估 config = default_config rc = StructuredDynamicFactorsRiskControl(config) stock1 = IndividualStockRisk( code="600000", name="浦发银行", volatility=0.3, fundamental=0.2, liquidity=0.1, valuation=0.4, recent_vol_change=1.5, recent_pct_change=-8.5, gap_down=True, finance_balance_change=-0.25, has_inst_sell=False, sector_name="银行", sector_style="金融", sector_total_gain=15.0, sector_position_ratio=0.18 ) result1 = rc.assess_stock(stock1) print(f"单个股评估 {result1['name']}({result1['code']}):") print(f" 总风险: {result1['total_risk']:.2f}") print(f" 风险等级: {result1['risk_level']}") print(f" 建议: {result1['suggestion']}") print(f" 警告: {result1['warnings']}") print() # 测试2: 板块择时 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 ) ] print(rc.get_sector_timing_report(sectors)) print() # 测试3: 组合评估 stocks = [stock1] portfolio_result = rc.assess_portfolio(stocks, sectors) print(rc.get_risk_report(portfolio_result))