auto_trade_sys/trading_system/risk_manager.py
薇薇安 8d5ea39bd4 a
2026-01-20 08:15:10 +08:00

763 lines
36 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
try:
from .binance_client import BinanceClient
from . import config
from .atr_strategy import ATRStrategy
except ImportError:
from binance_client import BinanceClient
import config
from atr_strategy import ATRStrategy
logger = logging.getLogger(__name__)
class RiskManager:
"""风险管理类"""
def __init__(self, client: BinanceClient):
"""
初始化风险管理器
Args:
client: 币安客户端
"""
self.client = client
# 不保存引用,每次都从 config.TRADING_CONFIG 读取最新配置
# self.config = config.TRADING_CONFIG # 移除,避免使用旧配置
# 初始化ATR策略
self.atr_strategy = ATRStrategy()
async def check_position_size(self, symbol: str, quantity: float, leverage: Optional[int] = None) -> bool:
"""
检查单笔仓位大小是否符合要求
Args:
symbol: 交易对
quantity: 下单数量
leverage: 杠杆倍数(用于换算保证金);若不传则使用配置的基础杠杆
Returns:
是否通过检查
"""
try:
logger.info(f"检查 {symbol} 单笔仓位大小...")
# 获取账户余额
balance = await self.client.get_account_balance()
available_balance = balance.get('available', 0)
if available_balance <= 0:
logger.warning(f"{symbol} 账户可用余额不足: {available_balance:.2f} USDT")
return False
# 计算名义价值与保证金(名义价值/杠杆)
ticker = await self.client.get_ticker_24h(symbol)
if not ticker:
logger.warning(f"{symbol} 无法获取价格数据")
return False
current_price = ticker['price']
notional_value = quantity * current_price
actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10)
if not actual_leverage or actual_leverage <= 0:
actual_leverage = 10
margin_value = notional_value / actual_leverage
# 重要语义POSITION_PERCENT 均按“保证金占用比例”计算(更符合 stop_loss/take_profit 的 margin 逻辑)
max_margin_value = available_balance * config.TRADING_CONFIG['MAX_POSITION_PERCENT']
min_margin_value = available_balance * config.TRADING_CONFIG['MIN_POSITION_PERCENT']
max_margin_pct = config.TRADING_CONFIG['MAX_POSITION_PERCENT'] * 100
min_margin_pct = config.TRADING_CONFIG['MIN_POSITION_PERCENT'] * 100
logger.info(f" 数量: {quantity:.4f}")
logger.info(f" 价格: {current_price:.4f} USDT")
logger.info(f" 名义价值: {notional_value:.2f} USDT")
logger.info(f" 杠杆: {actual_leverage}x")
logger.info(f" 保证金: {margin_value:.4f} USDT")
logger.info(f" 单笔最大保证金: {max_margin_value:.2f} USDT ({max_margin_pct:.1f}%)")
logger.info(f" 单笔最小保证金: {min_margin_value:.2f} USDT ({min_margin_pct:.1f}%)")
# 使用小的容差来处理浮点数精度问题0.01 USDT
tolerance = 0.01
if margin_value > max_margin_value + tolerance:
logger.warning(
f"{symbol} 单笔保证金过大: {margin_value:.4f} USDT > "
f"最大限制: {max_margin_value:.2f} USDT "
f"(超出: {margin_value - max_margin_value:.4f} USDT)"
)
return False
elif margin_value > max_margin_value:
# 在容差范围内,允许通过(浮点数精度问题)
logger.info(
f"{symbol} 保证金略超限制但 within 容差: "
f"{margin_value:.4f} USDT vs {max_margin_value:.2f} USDT "
f"(差异: {margin_value - max_margin_value:.4f} USDT)"
)
if margin_value < min_margin_value:
logger.warning(
f"{symbol} 单笔保证金过小: {margin_value:.4f} USDT < "
f"最小限制: {min_margin_value:.2f} USDT"
)
return False
logger.info(f"{symbol} 单笔仓位大小检查通过")
# 检查总仓位是否超过限制
logger.info(f"检查 {symbol} 总仓位限制...")
if not await self.check_total_position(margin_value):
return False
logger.info(
f"{symbol} 所有仓位检查通过: 保证金 {margin_value:.4f} USDT "
f"(账户可用余额: {available_balance:.2f} USDT)"
)
return True
except Exception as e:
logger.error(f"检查仓位大小失败 {symbol}: {e}", exc_info=True)
return False
async def check_total_position(self, new_position_margin: float) -> bool:
"""
检查总仓位是否超过限制
Args:
new_position_margin: 新仓位保证金占用USDT
Returns:
是否通过检查
"""
try:
# 获取当前持仓
positions = await self.client.get_open_positions()
# 计算当前总保证金占用
current_position_values = []
total_margin_value = 0
for pos in positions:
notional_value = abs(pos['positionAmt'] * pos['entryPrice'])
lv = pos.get('leverage', None)
try:
lv = int(lv) if lv is not None else None
except Exception:
lv = None
if not lv or lv <= 0:
lv = config.TRADING_CONFIG.get('LEVERAGE', 10) or 10
margin_value = notional_value / lv
current_position_values.append({
'symbol': pos['symbol'],
'notional': notional_value,
'margin': margin_value,
'leverage': lv,
'amount': pos['positionAmt'],
'entryPrice': pos['entryPrice']
})
total_margin_value += margin_value
# 加上新仓位
total_with_new = total_margin_value + new_position_margin
# 获取账户余额
balance = await self.client.get_account_balance()
total_balance = balance.get('total', 0)
available_balance = balance.get('available', 0)
if total_balance <= 0:
logger.warning("账户总余额为0无法开仓")
return False
max_total_margin = total_balance * config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT']
max_total_margin_pct = config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT'] * 100
# 详细日志
logger.info("=" * 60)
logger.info("总仓位检查详情:")
logger.info(f" 账户总余额: {total_balance:.2f} USDT")
logger.info(f" 账户可用余额: {available_balance:.2f} USDT")
logger.info(f" 总保证金上限: {max_total_margin:.2f} USDT ({max_total_margin_pct:.1f}%)")
logger.info(f" 当前持仓数量: {len(positions)}")
if current_position_values:
logger.info(" 当前持仓明细:")
for pos_info in current_position_values:
logger.info(
f" - {pos_info['symbol']}: "
f"保证金 {pos_info['margin']:.4f} USDT "
f"(名义 {pos_info['notional']:.2f} USDT, {pos_info['leverage']}x, "
f"数量: {pos_info['amount']:.4f}, 入场价: {pos_info['entryPrice']:.4f})"
)
logger.info(f" 当前总保证金: {total_margin_value:.4f} USDT")
logger.info(f" 新仓位保证金: {new_position_margin:.4f} USDT")
logger.info(f" 开仓后总保证金: {total_with_new:.4f} USDT")
logger.info(f" 剩余可用保证金: {max_total_margin - total_margin_value:.4f} USDT")
if total_with_new > max_total_margin:
logger.warning("=" * 60)
logger.warning(
f"❌ 总保证金超限: {total_with_new:.4f} USDT > "
f"最大限制: {max_total_margin:.2f} USDT"
)
logger.warning(
f" 超出: {total_with_new - max_total_margin:.4f} USDT "
f"({((total_with_new - max_total_margin) / max_total_margin * 100):.1f}%)"
)
logger.warning(" 建议: 平掉部分持仓或等待现有持仓平仓后再开新仓")
logger.warning("=" * 60)
return False
logger.info(
f"✓ 总保证金检查通过: {total_with_new:.4f} USDT / "
f"最大限制: {max_total_margin:.2f} USDT "
f"({(total_with_new / max_total_margin * 100):.1f}%)"
)
logger.info("=" * 60)
return True
except Exception as e:
logger.error(f"检查总仓位失败: {e}", exc_info=True)
return False
async def calculate_position_size(
self,
symbol: str,
change_percent: float,
leverage: Optional[int] = None
) -> Optional[float]:
"""
根据涨跌幅和风险参数计算合适的仓位大小
Args:
symbol: 交易对
change_percent: 涨跌幅百分比
Returns:
建议的仓位数量如果不符合条件则返回None
"""
try:
logger.info(f"开始计算 {symbol} 的仓位大小...")
# 获取账户余额
balance = await self.client.get_account_balance()
available_balance = balance.get('available', 0)
total_balance = balance.get('total', 0)
logger.info(f" 账户可用余额: {available_balance:.2f} USDT")
logger.info(f" 账户总余额: {total_balance:.2f} USDT")
if available_balance <= 0:
logger.warning(f"{symbol} 账户可用余额不足: {available_balance:.2f} USDT")
return None
# 获取当前价格
ticker = await self.client.get_ticker_24h(symbol)
if not ticker:
logger.warning(f"{symbol} 无法获取价格数据")
return None
current_price = ticker['price']
logger.info(f" 当前价格: {current_price:.4f} USDT")
# 重要语义MAX_POSITION_PERCENT 表示“单笔保证金占用比例”
# 先确定实际杠杆(用于从保证金换算名义价值)
actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10)
if not actual_leverage or actual_leverage <= 0:
actual_leverage = 10
# 根据涨跌幅调整仓位大小(涨跌幅越大,保证金占比可以适当增加)
base_position_percent = config.TRADING_CONFIG['MAX_POSITION_PERCENT']
max_position_percent = config.TRADING_CONFIG['MAX_POSITION_PERCENT']
min_position_percent = config.TRADING_CONFIG['MIN_POSITION_PERCENT']
# 涨跌幅超过5%时,可以适当增加保证金占比,但必须遵守 MAX_POSITION_PERCENT 上限
if abs(change_percent) > 5:
position_percent = min(
base_position_percent * 1.5,
max_position_percent
)
logger.info(f" 涨跌幅 {change_percent:.2f}% > 5%,使用增强仓位比例: {position_percent*100:.1f}%")
else:
position_percent = base_position_percent
logger.info(f" 涨跌幅 {change_percent:.2f}%,使用标准仓位比例: {position_percent*100:.1f}%")
# 计算保证金与名义价值
margin_value = available_balance * position_percent
notional_value = margin_value * actual_leverage
logger.info(f" 计算保证金: {margin_value:.4f} USDT ({position_percent*100:.1f}% of {available_balance:.2f})")
logger.info(f" 计算名义价值: {notional_value:.2f} USDT (保证金 {margin_value:.4f} × 杠杆 {actual_leverage}x)")
# 确保仓位价值满足最小名义价值要求币安要求至少5 USDT
min_notional = 5.0 # 币安合约最小名义价值
if notional_value < min_notional:
logger.warning(f" ⚠ 名义价值 {notional_value:.2f} USDT < 最小名义价值 {min_notional:.2f} USDT")
# 计算需要的最小保证金来满足最小名义价值margin >= min_notional / leverage
required_margin = min_notional / actual_leverage
required_margin_percent = required_margin / available_balance
logger.info(
f" 需要的最小保证金: {required_margin:.4f} USDT "
f"(占比 {required_margin_percent*100:.2f}%)"
)
# 检查是否可以使用更大的保证金占比(但不超过最大保证金限制)
if required_margin_percent <= max_position_percent:
position_percent = required_margin_percent
margin_value = required_margin
notional_value = min_notional
logger.info(f" ✓ 调整保证金占比到 {position_percent*100:.2f}% 以满足最小名义价值: {notional_value:.2f} USDT")
else:
# 即使使用最大仓位比例也无法满足最小名义价值
max_allowed_margin = available_balance * max_position_percent
max_allowed_notional = max_allowed_margin * actual_leverage
logger.warning(
f" ❌ 无法满足最小名义价值要求: "
f"需要 {min_notional:.2f} USDT (需要保证金 {required_margin:.4f} USDT)"
f"但最大允许名义 {max_allowed_notional:.2f} USDT (最大保证金 {max_allowed_margin:.2f} USDT)"
)
logger.warning(f" 💡 建议: 提高 MAX_POSITION_PERCENT 或降低杠杆/更换币种,确保最小名义价值可满足")
return None
# 计算数量(考虑合约的最小数量精度)
quantity = notional_value / current_price
logger.info(f" 计算数量: {quantity:.4f} (名义: {notional_value:.2f} / 价格: {current_price:.4f})")
# 验证计算出的数量对应的名义价值
calculated_notional = quantity * current_price
if calculated_notional < min_notional:
# 如果计算出的名义价值仍然不足,增加数量
required_quantity = min_notional / current_price
logger.warning(f" ⚠ 计算出的名义价值 {calculated_notional:.2f} USDT < {min_notional:.2f} USDT")
logger.info(f" ✓ 调整数量从 {quantity:.4f}{required_quantity:.4f}")
quantity = required_quantity
notional_value = required_quantity * current_price
margin_value = notional_value / actual_leverage
# 检查最小保证金要求margin 语义MIN_MARGIN_USDT 本身就是保证金下限)
min_margin_usdt = config.TRADING_CONFIG.get('MIN_MARGIN_USDT', 0.5) # 默认0.5 USDT
logger.info(f" 当前保证金: {margin_value:.4f} USDT (杠杆: {actual_leverage}x)")
if margin_value < min_margin_usdt:
# 保证金不足,需要增加保证金
logger.warning(
f" ⚠ 保证金 {margin_value:.4f} USDT < 最小保证金要求 {min_margin_usdt:.2f} USDT"
)
# 检查是否可以使用更大的仓位价值(但不超过最大仓位限制)
max_margin_value = available_balance * max_position_percent
if min_margin_usdt <= max_margin_value:
margin_value = min_margin_usdt
notional_value = margin_value * actual_leverage
quantity = notional_value / current_price
logger.info(
f" ✓ 调整保证金到 {margin_value:.2f} USDT "
f"(名义 {notional_value:.2f} USDT) 以满足最小保证金要求"
)
else:
# 即使使用最大仓位也无法满足最小保证金要求
max_margin = max_margin_value
logger.warning(
f" ❌ 无法满足最小保证金要求: "
f"需要 {min_margin_usdt:.2f} USDT 保证金,"
f"但最大允许 {max_margin:.2f} USDT 保证金 (MAX_POSITION_PERCENT={max_position_percent*100:.2f}%)"
)
logger.warning(
f" 💡 建议: 增加账户余额到至少 "
f"{min_margin_usdt / max_position_percent:.2f} USDT "
f"才能满足最小保证金要求"
)
return None
# 检查是否通过风险控制
logger.info(f" 检查仓位大小是否符合风险控制要求...")
# 计算最终的名义价值与保证金
final_notional_value = quantity * current_price
final_margin = final_notional_value / actual_leverage if actual_leverage > 0 else final_notional_value
# 添加最小名义价值检查0.2 USDT避免下无意义的小单子
MIN_NOTIONAL_VALUE = 0.2 # 最小名义价值0.2 USDT
if final_notional_value < MIN_NOTIONAL_VALUE:
logger.warning(
f"{symbol} 名义价值 {final_notional_value:.4f} USDT < 最小要求 {MIN_NOTIONAL_VALUE:.2f} USDT"
)
logger.warning(f" 💡 此类小单子意义不大,拒绝开仓")
return None
if await self.check_position_size(symbol, quantity, leverage=actual_leverage):
logger.info(
f"{symbol} 仓位计算成功: {quantity:.4f} "
f"(保证金: {final_margin:.4f} USDT, "
f"名义价值: {final_notional_value:.2f} USDT, "
f"保证金: {final_margin:.4f} USDT, 杠杆: {actual_leverage}x)"
)
return quantity
else:
logger.warning(f"{symbol} 仓位检查未通过,无法开仓")
return None
except Exception as e:
logger.error(f"计算仓位大小失败 {symbol}: {e}", exc_info=True)
return None
async def should_trade(self, symbol: str, change_percent: float) -> bool:
"""
判断是否应该交易
Args:
symbol: 交易对
change_percent: 涨跌幅百分比
Returns:
是否应该交易
"""
# 检查最小涨跌幅阈值
if abs(change_percent) < config.TRADING_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,
quantity: float,
leverage: int,
stop_loss_pct: Optional[float] = None,
klines: Optional[List] = None,
bollinger: Optional[Dict] = None,
atr: Optional[float] = None
) -> float:
"""
计算止损价格(基于保证金的盈亏金额)
Args:
entry_price: 入场价格
side: 方向 'BUY''SELL'
quantity: 持仓数量
leverage: 杠杆倍数
stop_loss_pct: 止损百分比相对于保证金如果为None则使用配置值
klines: K线数据用于计算支撑/阻力位(作为辅助参考)
bollinger: 布林带数据,用于计算动态止损(作为辅助参考)
atr: 平均真实波幅,用于计算动态止损(作为辅助参考)
Returns:
止损价格
"""
# 计算保证金和仓位价值
position_value = entry_price * quantity
margin = position_value / leverage if leverage > 0 else position_value
# 优先使用ATR动态止损如果启用且ATR可用
# 计算ATR百分比如果提供了ATR绝对值
atr_percent = None
if atr is not None and atr > 0 and entry_price > 0:
atr_percent = atr / entry_price
# 获取市场波动率(如果可用)
volatility = None # 可以从symbol_info中获取这里暂时为None
# 使用ATR策略计算止损
stop_loss_price_atr, stop_distance_atr, atr_details = self.atr_strategy.calculate_stop_loss(
entry_price, side, atr, atr_percent, volatility
)
if stop_loss_price_atr is None:
logger.debug(f"ATR不可用使用固定百分比止损")
# 获取止损百分比(相对于保证金)
stop_loss_percent = stop_loss_pct or config.TRADING_CONFIG['STOP_LOSS_PERCENT']
# 计算止损金额(相对于保证金)
stop_loss_amount = margin * stop_loss_percent
# 计算基于保证金的止损价
# 止损金额 = (开仓价 - 止损价) × 数量
# 所以:止损价 = 开仓价 - (止损金额 / 数量)
if side == 'BUY': # 做多,止损价低于入场价
stop_loss_price_margin = entry_price - (stop_loss_amount / quantity)
else: # 做空,止损价高于入场价
stop_loss_price_margin = entry_price + (stop_loss_amount / quantity)
# 同时计算基于价格百分比的止损价(作为最小值保护)
# 获取最小价格变动百分比(如果配置了)
min_price_change_pct = config.TRADING_CONFIG.get('MIN_STOP_LOSS_PRICE_PCT', None)
if min_price_change_pct is not None:
# 基于价格百分比的止损价
if side == 'BUY':
stop_loss_price_price = entry_price * (1 - min_price_change_pct)
else:
stop_loss_price_price = entry_price * (1 + min_price_change_pct)
else:
stop_loss_price_price = None
# 选择最终的止损价优先ATR其次保证金最后价格百分比取更宽松的
candidate_prices = []
if stop_loss_price_atr is not None:
candidate_prices.append(('ATR', stop_loss_price_atr))
candidate_prices.append(('保证金', stop_loss_price_margin))
if stop_loss_price_price is not None:
candidate_prices.append(('价格百分比', stop_loss_price_price))
# 选择“更宽松/更远”的止损:
# - 做多(BUY):止损越低越宽松 → 取最小值
# - 做空(SELL):止损越高越宽松 → 取最大值
if side == 'BUY':
stop_loss_price = min(p[1] for p in candidate_prices)
selected_method = [p[0] for p in candidate_prices if p[1] == stop_loss_price][0]
else:
stop_loss_price = max(p[1] for p in candidate_prices)
selected_method = [p[0] for p in candidate_prices if p[1] == stop_loss_price][0]
# 如果提供了技术分析数据,计算技术止损(允许更紧的止损,但需要在保证金止损范围内)
technical_stop = None
if klines and len(klines) >= 10:
# 计算支撑/阻力位
low_prices = [float(k[3]) for k in klines[-20:]] # 最近20根K线的最低价
high_prices = [float(k[2]) for k in klines[-20:]] # 最近20根K线的最高价
if side == 'BUY': # 做多,止损放在支撑位下方
# 找到近期波段低点
recent_low = min(low_prices)
# 止损放在低点下方0.5%
buffer = entry_price * 0.005 # 0.5%缓冲
technical_stop = recent_low - buffer
# 如果布林带可用,也可以考虑布林带下轨
if bollinger and bollinger.get('lower'):
bollinger_stop = bollinger['lower'] * 0.995 # 布林带下轨下方0.5%
technical_stop = max(technical_stop, bollinger_stop)
# 技术止损更紧,但需要确保在保证金止损范围内(不能超过保证金止损)
if technical_stop < stop_loss_price and technical_stop >= stop_loss_price_margin:
# 技术止损在合理范围内,可以考虑使用
candidate_prices.append(('技术分析', technical_stop))
logger.debug(
f"技术止损 (BUY): {technical_stop:.4f} "
f"(在保证金止损范围内)"
)
else: # 做空,止损放在阻力位上方
# 找到近期波段高点
recent_high = max(high_prices)
# 止损放在高点上方0.5%
buffer = entry_price * 0.005 # 0.5%缓冲
technical_stop = recent_high + buffer
# 如果布林带可用,也可以考虑布林带上轨
if bollinger and bollinger.get('upper'):
bollinger_stop = bollinger['upper'] * 1.005 # 布林带上轨上方0.5%
technical_stop = min(technical_stop, bollinger_stop)
# 技术止损更紧,但需要确保在保证金止损范围内(不能超过保证金止损)
if technical_stop > stop_loss_price and technical_stop <= stop_loss_price_margin:
# 技术止损在合理范围内,可以考虑使用
candidate_prices.append(('技术分析', technical_stop))
logger.debug(
f"技术止损 (SELL): {technical_stop:.4f} "
f"(在保证金止损范围内)"
)
# 重新选择最终的止损价(包括技术止损)
# 仍保持“更宽松/更远”的选择规则
if side == 'BUY':
final_stop_loss = min(p[1] for p in candidate_prices)
selected_method = [p[0] for p in candidate_prices if p[1] == final_stop_loss][0]
else:
final_stop_loss = max(p[1] for p in candidate_prices)
selected_method = [p[0] for p in candidate_prices if p[1] == final_stop_loss][0]
logger.info(
f"最终止损 ({side}): {final_stop_loss:.4f} (使用{selected_method}), "
+ (f"ATR={stop_loss_price_atr:.4f}, " if stop_loss_price_atr else "")
+ f"保证金={stop_loss_price_margin:.4f}, "
+ (f"价格={stop_loss_price_price:.4f}, " if stop_loss_price_price else "")
+ (f"技术={technical_stop:.4f}, " if technical_stop else "")
+ f"止损金额={stop_loss_amount:.2f} USDT ({stop_loss_percent*100:.1f}% of margin)"
)
return final_stop_loss
def get_take_profit_price(
self,
entry_price: float,
side: str,
quantity: float,
leverage: int,
take_profit_pct: Optional[float] = None,
atr: Optional[float] = None,
stop_distance: Optional[float] = None
) -> float:
"""
计算止盈价格基于保证金的盈亏金额支持ATR动态止盈
Args:
entry_price: 入场价格
side: 方向 'BUY''SELL'
quantity: 持仓数量
leverage: 杠杆倍数
take_profit_pct: 止盈百分比相对于保证金如果为None则使用配置值
atr: 平均真实波幅,用于计算动态止盈(可选)
Returns:
止盈价格
"""
# 计算保证金和仓位价值
position_value = entry_price * quantity
margin = position_value / leverage if leverage > 0 else position_value
# 优先使用ATR动态止盈如果启用且ATR可用
# 计算ATR百分比如果提供了ATR绝对值
atr_percent = None
if atr is not None and atr > 0 and entry_price > 0:
atr_percent = atr / entry_price
# 尝试从止损计算中获取止损距离(用于盈亏比计算)
# 如果止损已经计算过,可以使用止损距离来计算止盈
stop_distance_for_rr = None
# 注意这里无法直接获取止损距离需要调用方传递或者使用ATR倍数计算
# 使用ATR策略计算止盈
# 优先使用盈亏比方法基于止损距离如果没有止损距离则使用ATR倍数
take_profit_price_atr, take_profit_distance_atr, atr_tp_details = self.atr_strategy.calculate_take_profit(
entry_price, side, stop_distance, atr, atr_percent,
use_risk_reward_ratio=(stop_distance is not None)
)
if take_profit_price_atr is None:
logger.debug(f"ATR不可用使用固定百分比止盈")
# 获取止盈百分比(相对于保证金)
take_profit_percent = take_profit_pct or config.TRADING_CONFIG['TAKE_PROFIT_PERCENT']
# 计算止盈金额(相对于保证金)
take_profit_amount = margin * take_profit_percent
# 计算基于保证金的止盈价
# 止盈金额 = (止盈价 - 开仓价) × 数量
# 所以:止盈价 = 开仓价 + (止盈金额 / 数量)
if side == 'BUY': # 做多,止盈价高于入场价
take_profit_price_margin = entry_price + (take_profit_amount / quantity)
else: # 做空,止盈价低于入场价
take_profit_price_margin = entry_price - (take_profit_amount / quantity)
# 同时计算基于价格百分比的止盈价(作为最小值保护)
# 获取最小价格变动百分比(如果配置了)
min_price_change_pct = config.TRADING_CONFIG.get('MIN_TAKE_PROFIT_PRICE_PCT', None)
if min_price_change_pct is not None:
# 基于价格百分比的止盈价
if side == 'BUY':
take_profit_price_price = entry_price * (1 + min_price_change_pct)
else:
take_profit_price_price = entry_price * (1 - min_price_change_pct)
else:
take_profit_price_price = None
# 选择最终的止盈价优先ATR其次保证金最后价格百分比取更宽松的
candidate_prices = []
if take_profit_price_atr is not None:
candidate_prices.append(('ATR', take_profit_price_atr))
candidate_prices.append(('保证金', take_profit_price_margin))
if take_profit_price_price is not None:
candidate_prices.append(('价格百分比', take_profit_price_price))
# 对做多取最大的值(更宽松),对做空取最小的值(更宽松)
if side == 'BUY':
take_profit_price = max(p[1] for p in candidate_prices)
selected_method = [p[0] for p in candidate_prices if p[1] == take_profit_price][0]
else:
take_profit_price = min(p[1] for p in candidate_prices)
selected_method = [p[0] for p in candidate_prices if p[1] == take_profit_price][0]
logger.info(
f"止盈计算 ({side}): "
+ (f"ATR={take_profit_price_atr:.4f}, " if take_profit_price_atr else "")
+ f"基于保证金={take_profit_price_margin:.4f}, "
+ (f"基于价格={take_profit_price_price:.4f}, " if take_profit_price_price else "")
+ f"最终止盈={take_profit_price:.4f} (使用{selected_method}, 取更宽松), "
+ f"止盈金额={take_profit_amount:.4f} USDT ({take_profit_percent*100:.1f}% of margin)"
)
return take_profit_price
async def calculate_dynamic_leverage(self, signal_strength: int, symbol: str = None) -> int:
"""
根据信号强度计算动态杠杆倍数
信号强度越高,杠杆倍数越高,以最大化收益
同时检查交易对支持的最大杠杆限制
Args:
signal_strength: 信号强度 (0-10)
symbol: 交易对符号(可选,用于检查交易对的最大杠杆限制)
Returns:
杠杆倍数
"""
# 获取配置参数
use_dynamic_leverage = config.TRADING_CONFIG.get('USE_DYNAMIC_LEVERAGE', True)
base_leverage = config.TRADING_CONFIG.get('LEVERAGE', 10)
max_leverage = config.TRADING_CONFIG.get('MAX_LEVERAGE', 20)
min_signal_strength = config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 7)
# 如果未启用动态杠杆,返回基础杠杆
if not use_dynamic_leverage:
final_leverage = int(base_leverage)
else:
# 如果信号强度低于最小要求,使用基础杠杆
if signal_strength < min_signal_strength:
final_leverage = int(base_leverage)
else:
# 计算动态杠杆:信号强度越高,杠杆越高
# 公式:杠杆 = 基础杠杆 + (信号强度 - 最小信号强度) * (最大杠杆 - 基础杠杆) / (10 - 最小信号强度)
signal_range = 10 - min_signal_strength # 信号强度范围
leverage_range = max_leverage - base_leverage # 杠杆范围
if signal_range > 0:
# 计算信号强度超出最小值的比例
strength_above_min = signal_strength - min_signal_strength
leverage_increase = (strength_above_min / signal_range) * leverage_range
dynamic_leverage = base_leverage + leverage_increase
else:
dynamic_leverage = base_leverage
# 确保杠杆在合理范围内(不超过配置的最大杠杆)
final_leverage = max(int(base_leverage), min(int(dynamic_leverage), int(max_leverage)))
# 如果提供了交易对符号,检查交易对支持的最大杠杆限制
if symbol:
try:
symbol_info = await self.client.get_symbol_info(symbol)
if symbol_info and 'maxLeverage' in symbol_info:
symbol_max_leverage = symbol_info['maxLeverage']
if final_leverage > symbol_max_leverage:
logger.warning(
f"{symbol} 交易对最大杠杆限制为 {symbol_max_leverage}x, "
f"计算杠杆 {final_leverage}x 超过限制,调整为 {symbol_max_leverage}x"
)
final_leverage = symbol_max_leverage
except Exception as e:
logger.warning(f"获取 {symbol} 交易对杠杆限制失败: {e},使用计算值 {final_leverage}x")
logger.info(
f"动态杠杆计算: 信号强度={signal_strength}/10, "
f"基础杠杆={base_leverage}x, 计算杠杆={final_leverage}x"
+ (f", 交易对={symbol}" if symbol else "")
)
return final_leverage