auto_trade_sys/risk_manager.py
薇薇安 5c841621f7 a
2026-01-13 14:30:57 +08:00

256 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
风险管理模块 - 严格控制仓位和风险
"""
import logging
from typing import Dict, List, Optional
from binance_client import BinanceClient
import config
logger = logging.getLogger(__name__)
class RiskManager:
"""风险管理类"""
def __init__(self, client: BinanceClient):
"""
初始化风险管理器
Args:
client: 币安客户端
"""
self.client = client
self.config = config.TRADING_CONFIG
async def check_position_size(self, symbol: str, quantity: float) -> bool:
"""
检查单笔仓位大小是否符合要求
Args:
symbol: 交易对
quantity: 下单数量
Returns:
是否通过检查
"""
try:
# 获取账户余额
balance = await self.client.get_account_balance()
available_balance = balance.get('available', 0)
if available_balance <= 0:
logger.warning("账户余额不足")
return False
# 计算仓位价值(假设使用当前价格)
ticker = await self.client.get_ticker_24h(symbol)
if not ticker:
return False
current_price = ticker['price']
position_value = quantity * current_price
# 检查单笔仓位是否超过最大限制
max_position_value = available_balance * self.config['MAX_POSITION_PERCENT']
min_position_value = available_balance * self.config['MIN_POSITION_PERCENT']
if position_value > max_position_value:
logger.warning(
f"{symbol} 仓位过大: {position_value:.2f} USDT > "
f"最大限制: {max_position_value:.2f} USDT"
)
return False
if position_value < min_position_value:
logger.warning(
f"{symbol} 仓位过小: {position_value:.2f} USDT < "
f"最小限制: {min_position_value:.2f} USDT"
)
return False
# 检查总仓位是否超过限制
if not await self.check_total_position(position_value):
return False
logger.info(
f"{symbol} 仓位检查通过: {position_value:.2f} USDT "
f"(账户余额: {available_balance:.2f} USDT)"
)
return True
except Exception as e:
logger.error(f"检查仓位大小失败: {e}")
return False
async def check_total_position(self, new_position_value: float) -> bool:
"""
检查总仓位是否超过限制
Args:
new_position_value: 新仓位价值
Returns:
是否通过检查
"""
try:
# 获取当前持仓
positions = await self.client.get_open_positions()
# 计算当前总仓位价值
total_position_value = sum(
abs(pos['positionAmt'] * pos['entryPrice'])
for pos in positions
)
# 加上新仓位
total_position_value += new_position_value
# 获取账户余额
balance = await self.client.get_account_balance()
total_balance = balance.get('total', 0)
if total_balance <= 0:
return False
max_total_position = total_balance * self.config['MAX_TOTAL_POSITION_PERCENT']
if total_position_value > max_total_position:
logger.warning(
f"总仓位超限: {total_position_value:.2f} USDT > "
f"最大限制: {max_total_position:.2f} USDT"
)
return False
logger.info(
f"总仓位检查通过: {total_position_value:.2f} USDT / "
f"最大限制: {max_total_position:.2f} USDT"
)
return True
except Exception as e:
logger.error(f"检查总仓位失败: {e}")
return False
async def calculate_position_size(
self,
symbol: str,
change_percent: float
) -> Optional[float]:
"""
根据涨跌幅和风险参数计算合适的仓位大小
Args:
symbol: 交易对
change_percent: 涨跌幅百分比
Returns:
建议的仓位数量如果不符合条件则返回None
"""
try:
# 获取账户余额
balance = await self.client.get_account_balance()
available_balance = balance.get('available', 0)
if available_balance <= 0:
logger.warning("账户余额不足")
return None
# 获取当前价格
ticker = await self.client.get_ticker_24h(symbol)
if not ticker:
return None
current_price = ticker['price']
# 根据涨跌幅调整仓位大小(涨跌幅越大,仓位可以适当增加)
base_position_percent = self.config['MAX_POSITION_PERCENT']
# 涨跌幅超过5%时可以适当增加仓位但不超过1.5倍)
if abs(change_percent) > 5:
position_percent = min(
base_position_percent * 1.5,
self.config['MAX_POSITION_PERCENT'] * 1.5
)
else:
position_percent = base_position_percent
# 计算仓位价值
position_value = available_balance * position_percent
# 计算数量(考虑合约的最小数量精度)
quantity = position_value / current_price
# 检查是否通过风险控制
if await self.check_position_size(symbol, quantity):
return quantity
return None
except Exception as e:
logger.error(f"计算仓位大小失败: {e}")
return None
async def should_trade(self, symbol: str, change_percent: float) -> bool:
"""
判断是否应该交易
Args:
symbol: 交易对
change_percent: 涨跌幅百分比
Returns:
是否应该交易
"""
# 检查最小涨跌幅阈值
if abs(change_percent) < self.config['MIN_CHANGE_PERCENT']:
logger.debug(f"{symbol} 涨跌幅 {change_percent:.2f}% 小于阈值")
return False
# 检查是否已有持仓
positions = await self.client.get_open_positions()
existing_position = next(
(p for p in positions if p['symbol'] == symbol),
None
)
if existing_position:
logger.info(f"{symbol} 已有持仓,跳过")
return False
return True
def get_stop_loss_price(self, entry_price: float, side: str) -> float:
"""
计算止损价格
Args:
entry_price: 入场价格
side: 方向 'BUY''SELL'
Returns:
止损价格
"""
stop_loss_percent = self.config['STOP_LOSS_PERCENT']
if side == 'BUY': # 做多,止损价低于入场价
return entry_price * (1 - stop_loss_percent)
else: # 做空,止损价高于入场价
return entry_price * (1 + stop_loss_percent)
def get_take_profit_price(self, entry_price: float, side: str) -> float:
"""
计算止盈价格
Args:
entry_price: 入场价格
side: 方向 'BUY''SELL'
Returns:
止盈价格
"""
take_profit_percent = self.config['TAKE_PROFIT_PERCENT']
if side == 'BUY': # 做多,止盈价高于入场价
return entry_price * (1 + take_profit_percent)
else: # 做空,止盈价低于入场价
return entry_price * (1 - take_profit_percent)