# 风控管理模块 import pandas as pd from typing import Dict, Optional, Tuple from qihuo_analyzer.utils.technical_analysis import calculate_atr from qihuo_analyzer.core.models import StrategyConfig, RiskParams class RiskManager: """风险管理器""" def __init__(self, config: Optional[StrategyConfig] = None): self.config = config or StrategyConfig() def calculate_stop_loss(self, data: pd.DataFrame, entry_price: float, direction: str, atr_multiplier: Optional[float] = None) -> float: """计算止损位""" atr_multiplier = atr_multiplier or self.config.atr_multiplier # 计算ATR atr = calculate_atr(data).iloc[-1] # 根据方向计算止损位 if direction == 'long': stop_loss = entry_price - (atr * atr_multiplier) elif direction == 'short': stop_loss = entry_price + (atr * atr_multiplier) else: raise ValueError("Direction must be 'long' or 'short'") return stop_loss def calculate_position_size(self, account_balance: float, data: pd.DataFrame, direction: str, entry_price: float, contract_multiplier: float = 10, margin_rate: float = 0.1) -> Dict: """计算仓位大小""" # 计算ATR atr = calculate_atr(data).iloc[-1] # 计算每手风险 if direction == 'long': risk_per_unit = atr * self.config.atr_multiplier * contract_multiplier elif direction == 'short': risk_per_unit = atr * self.config.atr_multiplier * contract_multiplier else: raise ValueError("Direction must be 'long' or 'short'") # 计算最大风险金额 max_risk_amount = account_balance * self.config.max_risk_percent # 计算建议手数 suggested_units = max_risk_amount / risk_per_unit suggested_units = max(1, int(suggested_units)) # 至少1手 # 计算保证金需求 margin_per_unit = entry_price * contract_multiplier * margin_rate total_margin = suggested_units * margin_per_unit # 计算实际风险比例 actual_risk_percent = (risk_per_unit * suggested_units) / account_balance # 计算杠杆比例 leverage = (suggested_units * entry_price * contract_multiplier) / account_balance return { 'suggested_units': suggested_units, 'risk_per_unit': risk_per_unit, 'max_risk_amount': max_risk_amount, 'margin_per_unit': margin_per_unit, 'total_margin': total_margin, 'actual_risk_percent': actual_risk_percent, 'leverage': leverage, 'atr': atr } def calculate_profit_loss_ratio(self, entry_price: float, stop_loss: float, target_price: float, direction: str) -> float: """计算盈亏比""" if direction == 'long': profit = target_price - entry_price loss = entry_price - stop_loss elif direction == 'short': profit = entry_price - target_price loss = stop_loss - entry_price else: raise ValueError("Direction must be 'long' or 'short'") if loss == 0: return float('inf') return profit / loss def validate_trade(self, account_balance: float, data: pd.DataFrame, direction: str, entry_price: float, target_price: float, contract_multiplier: float = 10, margin_rate: float = 0.1) -> Dict: """验证交易是否符合风控要求""" # 计算止损位 stop_loss = self.calculate_stop_loss(data, entry_price, direction) # 计算盈亏比 pl_ratio = self.calculate_profit_loss_ratio(entry_price, stop_loss, target_price, direction) # 计算仓位大小 position_info = self.calculate_position_size(account_balance, data, direction, entry_price, contract_multiplier, margin_rate) # 检查各项风控指标 checks = { 'profit_loss_ratio': { 'value': pl_ratio, 'required': self.config.min_profit_loss_ratio, 'pass': pl_ratio >= self.config.min_profit_loss_ratio }, 'risk_percent': { 'value': position_info['actual_risk_percent'] * 100, 'required': self.config.max_risk_percent * 100, 'pass': position_info['actual_risk_percent'] <= self.config.max_risk_percent }, 'leverage': { 'value': position_info['leverage'], 'required': 5, # 最大杠杆 'pass': position_info['leverage'] <= 5 }, 'margin_utilization': { 'value': (position_info['total_margin'] / account_balance) * 100, 'required': 30, # 最大保证金使用率 'pass': (position_info['total_margin'] / account_balance) <= 0.3 } } # 综合判断 all_passed = all(check['pass'] for check in checks.values()) return { 'valid': all_passed, 'checks': checks, 'position_info': position_info, 'stop_loss': stop_loss, 'profit_loss_ratio': pl_ratio } def generate_risk_report(self, account_balance: float, data: pd.DataFrame, direction: str, entry_price: float, target_price: float, contract_multiplier: float = 10, margin_rate: float = 0.1) -> Dict: """生成风险报告""" # 验证交易 validation_result = self.validate_trade(account_balance, data, direction, entry_price, target_price, contract_multiplier, margin_rate) # 生成风险建议 suggestions = [] if not validation_result['checks']['profit_loss_ratio']['pass']: suggestions.append(f"盈亏比不足,建议调整目标价至{self._calculate_adjusted_target(entry_price, validation_result['stop_loss'], direction):.2f}") if not validation_result['checks']['risk_percent']['pass']: suggestions.append(f"风险比例过高,建议减少仓位至{int(validation_result['position_info']['suggested_units'] * 0.8)}手") if not validation_result['checks']['leverage']['pass']: suggestions.append("杠杆比例过高,建议降低仓位") if not validation_result['checks']['margin_utilization']['pass']: suggestions.append("保证金使用率过高,建议减少仓位") # 计算风险回报比 risk_return_ratio = self._calculate_risk_return_ratio(validation_result['profit_loss_ratio'], validation_result['position_info']['actual_risk_percent']) report = { 'account_balance': account_balance, 'direction': direction, 'entry_price': entry_price, 'stop_loss': validation_result['stop_loss'], 'target_price': target_price, 'profit_loss_ratio': validation_result['profit_loss_ratio'], 'position_info': validation_result['position_info'], 'risk_metrics': { 'risk_return_ratio': risk_return_ratio, 'max_drawdown_estimate': self._estimate_max_drawdown(account_balance, validation_result['position_info']), 'recovery_factor': self._calculate_recovery_factor(risk_return_ratio) }, 'suggestions': suggestions, 'validation_result': validation_result } return report def _calculate_adjusted_target(self, entry_price: float, stop_loss: float, direction: str) -> float: """计算调整后的目标价""" if direction == 'long': loss = entry_price - stop_loss required_profit = loss * self.config.min_profit_loss_ratio return entry_price + required_profit elif direction == 'short': loss = stop_loss - entry_price required_profit = loss * self.config.min_profit_loss_ratio return entry_price - required_profit else: raise ValueError("Direction must be 'long' or 'short'") def _calculate_risk_return_ratio(self, pl_ratio: float, risk_percent: float) -> float: """计算风险回报比""" return pl_ratio * (1 - risk_percent) def _estimate_max_drawdown(self, account_balance: float, position_info: Dict) -> float: """估算最大回撤""" max_loss = position_info['risk_per_unit'] * position_info['suggested_units'] return (max_loss / account_balance) * 100 def _calculate_recovery_factor(self, risk_return_ratio: float) -> float: """计算恢复因子""" if risk_return_ratio <= 0: return 0 return risk_return_ratio * 0.8 def monitor_position_risk(self, current_price: float, entry_price: float, stop_loss: float, target_price: float, direction: str, units: int, contract_multiplier: float = 10) -> Dict: """监控持仓风险""" # 计算当前盈亏 if direction == 'long': current_profit = (current_price - entry_price) * units * contract_multiplier distance_to_stop = entry_price - current_price distance_to_target = target_price - current_price elif direction == 'short': current_profit = (entry_price - current_price) * units * contract_multiplier distance_to_stop = current_price - entry_price distance_to_target = entry_price - current_price else: raise ValueError("Direction must be 'long' or 'short'") # 计算浮盈比例 unrealized_pnl_percent = (current_profit / (entry_price * units * contract_multiplier)) * 100 # 计算止损触发距离 stop_percent = (distance_to_stop / entry_price) * 100 # 计算目标达成距离 target_percent = (distance_to_target / entry_price) * 100 # 风险状态评估 risk_status = self._assess_risk_status(current_price, stop_loss, target_price, direction) return { 'current_price': current_price, 'entry_price': entry_price, 'stop_loss': stop_loss, 'target_price': target_price, 'current_profit': current_profit, 'unrealized_pnl_percent': unrealized_pnl_percent, 'distance_to_stop': distance_to_stop, 'distance_to_target': distance_to_target, 'stop_percent': stop_percent, 'target_percent': target_percent, 'risk_status': risk_status } def _assess_risk_status(self, current_price: float, stop_loss: float, target_price: float, direction: str) -> str: """评估风险状态""" if direction == 'long': if current_price <= stop_loss: return 'stop_loss_triggered' elif current_price >= target_price: return 'target_reached' elif current_price > stop_loss * 1.05: return 'low_risk' else: return 'medium_risk' elif direction == 'short': if current_price >= stop_loss: return 'stop_loss_triggered' elif current_price <= target_price: return 'target_reached' elif current_price < stop_loss * 0.95: return 'low_risk' else: return 'medium_risk' else: raise ValueError("Direction must be 'long' or 'short'")