Files
AI-Trader/market/services/strategy/risk_manager.py
T

257 lines
8.8 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
风险管理服务
"""
from typing import Dict, Optional
from datetime import datetime
from ...models import TradingStrategy
class RiskManager:
"""风险管理服务"""
def __init__(self):
# 账户信息(从外部更新)
self._account_balance: float = 0.0
self._account_equity: float = 0.0
self._free_margin: float = 0.0
# 每日风险限制
self._daily_risk_limit: float = 5.0 # 每日最大风险百分比
self._daily_risk_used: float = 0.0 # 今日已使用风险
# 品种配置(点值、最小手数等)
self._symbol_config: Dict[str, Dict] = {}
# 统计服务引用(用于获取账户信息)
self._statistics_service = None
print("[RiskManager] 风险管理服务已初始化")
def set_statistics_service(self, service) -> None:
"""设置统计服务引用"""
self._statistics_service = service
def _refresh_account_info(self) -> None:
"""从统计服务刷新账户信息"""
if not self._statistics_service:
return
try:
account_info = self._statistics_service.get_account_info()
if account_info:
self._account_balance = account_info.get('balance', 0.0)
self._account_equity = account_info.get('equity', 0.0)
# free_margin 通常等于 equity - used_margin,这里用 equity 近似
self._free_margin = account_info.get('equity', 0.0)
except Exception as e:
print(f"[RiskManager] 刷新账户信息失败: {e}")
# ==================== 账户信息 ====================
def update_account_info(self, balance: float, equity: float, free_margin: float) -> None:
"""更新账户信息"""
self._account_balance = balance
self._account_equity = equity
self._free_margin = free_margin
def get_account_balance(self) -> float:
"""获取账户余额"""
return self._account_balance
def get_account_equity(self) -> float:
"""获取账户权益"""
return self._account_equity
# ==================== 品种配置 ====================
def set_symbol_config(self, symbol: str, config: Dict) -> None:
"""设置品种配置"""
self._symbol_config[symbol] = config
def get_symbol_config(self, symbol: str) -> Dict:
"""获取品种配置"""
return self._symbol_config.get(symbol, {
"point_value": 1.0, # 点值
"min_volume": 0.01, # 最小手数
"max_volume": 10.0, # 最大手数
"volume_step": 0.01, # 手数步长
})
# ==================== 手数计算 ====================
def calculate_volume(self, symbol: str, risk_points: float,
strategy: TradingStrategy) -> float:
"""
计算交易手数
Args:
symbol: 品种
risk_points: 风险点数
strategy: 策略配置
Returns:
计算的手数
"""
config = self.get_symbol_config(symbol)
point_value = config.get('point_value', 1.0)
min_volume = config.get('min_volume', 0.01)
max_volume = config.get('max_volume', 10.0)
volume_step = config.get('volume_step', 0.01)
if strategy.volume_mode == "fixed":
volume = strategy.fixed_volume
elif strategy.volume_mode == "risk_percent":
# 根据风险百分比计算手数
risk_amount = self._account_balance * (strategy.risk_percent / 100)
# 手数 = 风险金额 / (风险点数 * 点值)
if risk_points > 0 and point_value > 0:
volume = risk_amount / (risk_points * point_value)
else:
volume = min_volume
else:
volume = strategy.fixed_volume
# 应用最大风险点数限制
if risk_points > strategy.max_risk_points:
print(f"[RiskManager] 风险点数 {risk_points} 超过最大限制 {strategy.max_risk_points}")
return 0.0
# 限制手数范围
volume = max(min_volume, min(volume, max_volume))
# 按步长取整
volume = round(volume / volume_step) * volume_step
return volume
# ==================== 风险检查 ====================
def check_risk(self, symbol: str, volume: float, risk_points: float) -> Dict:
"""
检查交易风险
Args:
symbol: 品种
volume: 手数
risk_points: 风险点数
Returns:
检查结果
"""
# 刷新账户信息
self._refresh_account_info()
config = self.get_symbol_config(symbol)
point_value = config.get('point_value', 1.0)
# 计算风险金额
risk_amount = volume * risk_points * point_value
risk_percent = (risk_amount / self._account_balance * 100) if self._account_balance > 0 else 0
# 检查每日风险限制
remaining_risk = self._daily_risk_limit - self._daily_risk_used
allowed = True
warnings = []
# 账户信息是否已初始化
account_initialized = self._account_balance > 0 or self._free_margin > 0
if risk_percent > 5:
allowed = False
warnings.append(f"单笔风险 {risk_percent:.2f}% 超过5%")
if risk_percent + self._daily_risk_used > self._daily_risk_limit:
allowed = False
warnings.append(f"将超过每日风险限制 {self._daily_risk_limit}%")
# 只有账户信息已初始化时才检查保证金
if account_initialized and self._free_margin < risk_amount:
allowed = False
warnings.append(f"保证金不足 (可用: {self._free_margin:.2f}, 需要: {risk_amount:.2f})")
if not account_initialized:
warnings.append("账户信息未初始化,跳过保证金检查")
return {
"allowed": allowed,
"risk_amount": risk_amount,
"risk_percent": round(risk_percent, 2),
"daily_risk_used": self._daily_risk_used,
"daily_risk_limit": self._daily_risk_limit,
"remaining_risk": remaining_risk,
"warnings": warnings,
"account_initialized": account_initialized,
}
# ==================== 持仓检查 ====================
def check_position_limit(self, symbol: str, strategy: TradingStrategy,
current_positions: int, same_direction: int,
opposite_direction: int, action: str) -> Dict:
"""
检查持仓限制
Args:
symbol: 品种
strategy: 策略配置
current_positions: 当前持仓数
same_direction: 同向持仓数
opposite_direction: 反向持仓数
action: 交易方向 buy/sell
Returns:
检查结果
"""
allowed = True
warnings = []
# 检查最大持仓数
if current_positions >= strategy.max_positions:
allowed = False
warnings.append(f"已达到最大持仓数 {strategy.max_positions}")
# 检查同向持仓
new_same_direction = same_direction + 1
if new_same_direction > strategy.max_same_direction:
allowed = False
warnings.append(f"同向持仓将超过限制 {strategy.max_same_direction}")
# 检查持仓冲突策略
if opposite_direction > 0:
if strategy.position_conflict == "block":
allowed = False
warnings.append("有反向持仓,策略禁止新开仓")
elif strategy.position_conflict == "allow_same":
allowed = False
warnings.append("有反向持仓,策略只允许同向加仓")
elif strategy.position_conflict == "allow_opposite":
# 允许反向
pass
return {
"allowed": allowed,
"current_positions": current_positions,
"same_direction": same_direction,
"opposite_direction": opposite_direction,
"max_positions": strategy.max_positions,
"max_same_direction": strategy.max_same_direction,
"warnings": warnings,
}
# ==================== 状态 ====================
def get_status(self) -> Dict:
"""获取状态"""
return {
"account_balance": self._account_balance,
"account_equity": self._account_equity,
"free_margin": self._free_margin,
"daily_risk_limit": self._daily_risk_limit,
"daily_risk_used": self._daily_risk_used,
"symbol_count": len(self._symbol_config),
}