257 lines
8.8 KiB
Python
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),
|
|
} |