Files
sanguo_quant_live/strategies/factors-dynamic-weight-timing-20260327/risk_control.py
T

291 lines
10 KiB
Python

"""
风控模块 - 量化策略风控系统
功能:
1. 单票15%止损规则
2. 整体回撤分级风控(10%/20%/25% 分级降仓)
3. 黑天鹅过滤(ST、跌停、财务造假排除)
Author: 关羽(云长)
Date: 2026-03-27
"""
from dataclasses import dataclass
from typing import List, Dict, Optional
import pandas as pd
@dataclass
class StockInfo:
"""单票基本信息"""
code: str
name: str
cost_price: float
current_price: float
is_st: bool = False
is_limit_down: bool = False
is_fraud: bool = False
volume: float = 0.0 # 日成交额(亿)
@dataclass
class PortfolioInfo:
"""组合信息"""
total_capital: float
current_capital: float
positions: Dict[str, float] # code -> position_size
class SingleStockRiskControl:
"""单票风控:15%止损规则"""
def __init__(self, stop_loss_pct: float = 0.15):
"""
初始化
:param stop_loss_pct: 止损比例,默认15%
"""
self.stop_loss_pct = stop_loss_pct
def check_stop_loss(self, stock: StockInfo) -> bool:
"""
检查是否触发止损
:return: True = 需要止损,False = 持有
"""
if stock.cost_price <= 0:
return False
drawdown = (stock.current_price - stock.cost_price) / stock.cost_price
# 亏损超过止损比例,触发止损
return drawdown <= -self.stop_loss_pct
def get_drawdown(self, stock: StockInfo) -> float:
"""计算单票当前回撤"""
if stock.cost_price <= 0:
return 0.0
return (stock.current_price - stock.cost_price) / stock.cost_price
class PortfolioDrawdownRiskControl:
"""
整体回撤分级风控
10%回撤 → 降仓50%
20%回撤 → 降仓75%
25%回撤 → 清仓休息
"""
def __init__(self,
drawdown_levels: List[float] = None,
reduce_ratios: List[float] = None):
"""
初始化分级风控
:param drawdown_levels: 回撤阈值
:param reduce_ratios: 对应降仓比例(剩余仓位比例)
"""
# 默认分级:10%/20%/25%
self.drawdown_levels = drawdown_levels or [0.10, 0.20, 0.25]
# 对应剩余仓位:50% / 25% / 0%
self.reduce_ratios = reduce_ratios or [0.50, 0.25, 0.00]
def calculate_total_drawdown(self, portfolio: PortfolioInfo) -> float:
"""计算组合总回撤"""
if portfolio.total_capital <= 0:
return 0.0
return (portfolio.total_capital - portfolio.current_capital) / portfolio.total_capital
def get_target_position_ratio(self, portfolio: PortfolioInfo) -> float:
"""
获取目标仓位比例
:return: 目标仓位占当前总资金的比例
"""
drawdown = self.calculate_total_drawdown(portfolio)
# 从大到小检查,触发最高级别
for level, ratio in reversed(list(zip(self.drawdown_levels, self.reduce_ratios))):
if drawdown >= level:
return ratio
# 没有触发任何分级,保持满仓
return 1.0
def need_rebalance(self, portfolio: PortfolioInfo) -> tuple[bool, float]:
"""
检查是否需要降仓
:return: (是否需要调整, 目标仓位比例)
"""
target_ratio = self.get_target_position_ratio(portfolio)
current_ratio = sum(portfolio.positions.values()) / portfolio.current_capital
# 当前仓位高于目标,需要降仓
if current_ratio > target_ratio + 0.05: # 5%容差
return True, target_ratio
return False, target_ratio
class BlackSwanFilter:
"""黑天鹅过滤:排除ST、跌停、财务造假、低流动性票"""
def __init__(self, min_daily_volume: float = 0.5):
"""
初始化过滤器
:param min_daily_volume: 最小日成交额(亿),低于此排除
"""
self.min_daily_volume = min_daily_volume
def filter_stock(self, stock: StockInfo) -> tuple[bool, str]:
"""
过滤个股
:return: (是否通过过滤, 不通过原因)
True = 可以买入,False = 排除
"""
# 1. 排除ST股
if stock.is_st:
return False, "ST股票"
# 2. 排除跌停
if stock.is_limit_down:
return False, "跌停股票"
# 3. 排除财务造假
if stock.is_fraud:
return False, "财务造假问题股"
# 4. 排除低流动性
if stock.volume < self.min_daily_volume and stock.volume > 0:
return False, f"成交额不足{self.min_daily_volume}亿,流动性不足"
# 全部通过
return True, ""
def filter_universe(self, stocks: List[StockInfo]) -> List[StockInfo]:
"""批量过滤选股池"""
passed = []
for stock in stocks:
ok, _ = self.filter_stock(stock)
if ok:
passed.append(stock)
return passed
class RiskController:
"""总风控控制器,整合所有风控规则"""
def __init__(self):
self.single_stock_rc = SingleStockRiskControl()
self.portfolio_rc = PortfolioDrawdownRiskControl()
self.black_swan_filter = BlackSwanFilter()
def pre_trade_check(self, stock: StockInfo, portfolio: PortfolioInfo) -> tuple[bool, str]:
"""
交易前检查:开仓前调用
:return: (是否允许开仓, 原因)
"""
# 第一步:黑天鹅过滤
ok, reason = self.black_swan_filter.filter_stock(stock)
if not ok:
return False, f"黑天鹅过滤: {reason}"
# 第二步:检查整体回撤是否允许新开仓
target_ratio = self.portfolio_rc.get_target_position_ratio(portfolio)
current_ratio = sum(portfolio.positions.values()) / portfolio.current_capital
if current_ratio >= target_ratio:
return False, f"整体回撤已触发降仓,当前仓位{current_ratio:.1%},目标仓位{target_ratio:.1%},不允许新开仓"
return True, ""
def post_trade_check(self, stocks: List[StockInfo], portfolio: PortfolioInfo) -> dict:
"""
收盘后检查:止损检查 + 降仓检查
:return: 风控结果,包含需要止损的票和需要降仓的信息
"""
# 1. 检查单票止损
stop_loss_list = []
for stock in stocks:
if self.single_stock_rc.check_stop_loss(stock):
stop_loss_list.append({
"code": stock.code,
"name": stock.name,
"current_drawdown": self.single_stock_rc.get_drawdown(stock)
})
# 2. 检查组合降仓
need_rebalance, target_ratio = self.portfolio_rc.need_rebalance(portfolio)
current_drawdown = self.portfolio_rc.calculate_total_drawdown(portfolio)
return {
"stop_loss_required": len(stop_loss_list) > 0,
"stop_loss_stocks": stop_loss_list,
"rebalance_required": need_rebalance,
"current_drawdown": current_drawdown,
"target_position_ratio": target_ratio,
"current_position_ratio": sum(portfolio.positions.values()) / portfolio.current_capital if portfolio.current_capital > 0 else 0
}
def get_risk_report(self, stocks: List[StockInfo], portfolio: PortfolioInfo) -> str:
"""生成风控报告"""
result = self.post_trade_check(stocks, portfolio)
report = []
report.append("=" * 50)
report.append("风控日报")
report.append("=" * 50)
report.append(f"组合总回撤: {result['current_drawdown']:.2%}")
report.append(f"当前仓位比例: {result['current_position_ratio']:.2%}")
report.append(f"目标仓位比例: {result['target_position_ratio']:.2%}")
report.append("")
if result['stop_loss_required']:
report.append("触发止损个股:")
for s in result['stop_loss_stocks']:
report.append(f" {s['code']} {s['name']} 回撤: {s['current_drawdown']:.2%}")
else:
report.append("无个股触发止损")
report.append("")
if result['rebalance_required']:
report.append(f"⚠️ 需要降仓调整,当前仓位高于目标")
else:
report.append("✅ 仓位符合风控要求")
report.append("=" * 50)
return "\n".join(report)
if __name__ == "__main__":
# 简单测试
from pprint import pprint
# 测试单票止损
stock1 = StockInfo(code="000001", name="平安银行", cost_price=10.0, current_price=8.4)
rc_single = SingleStockRiskControl()
print(f"测试单票止损,当前价8.4,成本10,亏损16%: {rc_single.check_stop_loss(stock1)} → 应该是True")
stock2 = StockInfo(code="000002", name="万科A", cost_price=20.0, current_price=18.0)
print(f"测试单票止损,当前价18,成本20,亏损10%: {rc_single.check_stop_loss(stock2)} → 应该是False")
# 测试组合回撤
portfolio = PortfolioInfo(
total_capital=1000000,
current_capital=850000,
positions={"000001": 200000, "000002": 150000}
)
rc_port = PortfolioDrawdownRiskControl()
dd = rc_port.calculate_total_drawdown(portfolio)
print(f"\n组合回撤: {dd:.2%} → 15%")
print(f"目标仓位比例: {rc_port.get_target_position_ratio(portfolio)} → 50%(触发10%档)")
# 测试黑天鹅过滤
filter_bs = BlackSwanFilter()
st_stock = StockInfo(code="000003", name="ST基蛋", cost_price=10, current_price=10, is_st=True)
ok, reason = filter_bs.filter_stock(st_stock)
print(f"\nST过滤: ok={ok}, reason={reason}")
normal_stock = StockInfo(code="600000", name="浦发银行", cost_price=10, current_price=10, volume=1.2)
ok, reason = filter_bs.filter_stock(normal_stock)
print(f"正常票过滤: ok={ok}, reason={reason}")
# 整合风控测试
rc = RiskController()
print("\n风控报告:")
print(rc.get_risk_report([stock1, stock2], portfolio))