You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
12 KiB

# 风控管理模块
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'")