""" 风险管理模块 - 严格控制仓位和风险 """ 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) -> bool: """ 检查单笔仓位大小是否符合要求 Args: symbol: 交易对 quantity: 下单数量 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'] position_value = quantity * current_price # 检查单笔仓位是否超过最大限制(每次都从最新配置读取) max_position_value = available_balance * config.TRADING_CONFIG['MAX_POSITION_PERCENT'] min_position_value = available_balance * config.TRADING_CONFIG['MIN_POSITION_PERCENT'] max_position_pct = config.TRADING_CONFIG['MAX_POSITION_PERCENT'] * 100 min_position_pct = config.TRADING_CONFIG['MIN_POSITION_PERCENT'] * 100 logger.info(f" 数量: {quantity:.4f}") logger.info(f" 价格: {current_price:.4f} USDT") logger.info(f" 仓位价值: {position_value:.2f} USDT") logger.info(f" 单笔最大限制: {max_position_value:.2f} USDT ({max_position_pct:.1f}%)") logger.info(f" 单笔最小限制: {min_position_value:.2f} USDT ({min_position_pct:.1f}%)") # 使用小的容差来处理浮点数精度问题(0.01 USDT) tolerance = 0.01 if position_value > max_position_value + tolerance: logger.warning( f"❌ {symbol} 单笔仓位过大: {position_value:.2f} USDT > " f"最大限制: {max_position_value:.2f} USDT " f"(超出: {position_value - max_position_value:.2f} USDT)" ) return False elif position_value > max_position_value: # 在容差范围内,允许通过(浮点数精度问题) logger.info( f"⚠ {symbol} 仓位价值略超限制但 within 容差: " f"{position_value:.2f} USDT vs {max_position_value:.2f} USDT " f"(差异: {position_value - max_position_value:.4f} USDT)" ) if position_value < min_position_value: logger.warning( f"❌ {symbol} 单笔仓位过小: {position_value:.2f} USDT < " f"最小限制: {min_position_value:.2f} USDT" ) return False logger.info(f"✓ {symbol} 单笔仓位大小检查通过") # 检查总仓位是否超过限制 logger.info(f"检查 {symbol} 总仓位限制...") 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"检查仓位大小失败 {symbol}: {e}", exc_info=True) 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() # 计算当前总仓位价值 current_position_values = [] total_position_value = 0 for pos in positions: position_value = abs(pos['positionAmt'] * pos['entryPrice']) current_position_values.append({ 'symbol': pos['symbol'], 'value': position_value, 'amount': pos['positionAmt'], 'entryPrice': pos['entryPrice'] }) total_position_value += position_value # 加上新仓位 total_with_new = total_position_value + new_position_value # 获取账户余额 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_position = total_balance * config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT'] max_total_position_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_position:.2f} USDT ({max_total_position_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['value']:.2f} USDT " f"(数量: {pos_info['amount']:.4f}, " f"入场价: {pos_info['entryPrice']:.4f})" ) logger.info(f" 当前总仓位: {total_position_value:.2f} USDT") logger.info(f" 新仓位价值: {new_position_value:.2f} USDT") logger.info(f" 开仓后总仓位: {total_with_new:.2f} USDT") logger.info(f" 剩余可用仓位: {max_total_position - total_position_value:.2f} USDT") if total_with_new > max_total_position: logger.warning("=" * 60) logger.warning( f"❌ 总仓位超限: {total_with_new:.2f} USDT > " f"最大限制: {max_total_position:.2f} USDT" ) logger.warning( f" 超出: {total_with_new - max_total_position:.2f} USDT " f"({((total_with_new - max_total_position) / max_total_position * 100):.1f}%)" ) logger.warning(" 建议: 平掉部分持仓或等待现有持仓平仓后再开新仓") logger.warning("=" * 60) return False logger.info( f"✓ 总仓位检查通过: {total_with_new:.2f} USDT / " f"最大限制: {max_total_position:.2f} USDT " f"({(total_with_new / max_total_position * 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") # 根据涨跌幅调整仓位大小(涨跌幅越大,仓位可以适当增加) 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%时,可以适当增加仓位(但不超过1.5倍) if abs(change_percent) > 5: position_percent = min( base_position_percent * 1.5, max_position_percent * 1.5 ) 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}%") # 计算仓位价值 position_value = available_balance * position_percent logger.info(f" 计算仓位价值: {position_value:.2f} USDT ({position_percent*100:.1f}% of {available_balance:.2f})") # 确保仓位价值满足最小名义价值要求(币安要求至少5 USDT) min_notional = 5.0 # 币安合约最小名义价值 if position_value < min_notional: logger.warning(f" ⚠ 仓位价值 {position_value:.2f} USDT < 最小名义价值 {min_notional:.2f} USDT") # 计算需要的最小仓位比例来满足最小名义价值 required_position_percent = min_notional / available_balance logger.info(f" 需要的最小仓位比例: {required_position_percent*100:.2f}% (最小名义价值 {min_notional:.2f} USDT / 可用余额 {available_balance:.2f} USDT)") # 检查是否可以使用更大的仓位比例(但不超过最大仓位限制) if required_position_percent <= max_position_percent: # 可以使用更大的仓位比例来满足最小名义价值 position_percent = required_position_percent position_value = min_notional logger.info(f" ✓ 调整仓位比例到 {position_percent*100:.2f}% 以满足最小名义价值: {position_value:.2f} USDT") else: # 即使使用最大仓位比例也无法满足最小名义价值 max_allowed_value = available_balance * max_position_percent logger.warning( f" ❌ 无法满足最小名义价值要求: " f"需要 {min_notional:.2f} USDT (仓位比例 {required_position_percent*100:.2f}%)," f"但最大允许 {max_allowed_value:.2f} USDT (仓位比例 {max_position_percent*100:.1f}%)" ) logger.warning(f" 💡 建议: 增加账户余额到至少 {min_notional / max_position_percent:.2f} USDT 才能满足最小名义价值要求") return None # 计算数量(考虑合约的最小数量精度) quantity = position_value / current_price logger.info(f" 计算数量: {quantity:.4f} (价值: {position_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 position_value = required_quantity * current_price # 检查最小保证金要求(如果保证金小于此值,自动调整到0.5U保证金) min_margin_usdt = config.TRADING_CONFIG.get('MIN_MARGIN_USDT', 0.5) # 默认0.5 USDT # 使用传入的实际杠杆,如果没有传入则使用配置的基础杠杆 actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10) # 计算实际需要的保证金 = 仓位价值 / 杠杆 required_margin = position_value / actual_leverage logger.info(f" 计算保证金: {required_margin:.4f} USDT (仓位价值: {position_value:.2f} USDT / 杠杆: {actual_leverage}x)") if required_margin < min_margin_usdt: # 保证金不足,需要增加仓位价值 required_position_value = min_margin_usdt * actual_leverage logger.warning( f" ⚠ 保证金 {required_margin:.4f} USDT < 最小保证金要求 {min_margin_usdt:.2f} USDT" ) logger.info( f" 需要的最小仓位价值: {required_position_value:.2f} USDT " f"(最小保证金 {min_margin_usdt:.2f} USDT × 杠杆 {actual_leverage}x)" ) # 检查是否可以使用更大的仓位价值(但不超过最大仓位限制) max_position_value = available_balance * max_position_percent if required_position_value <= max_position_value: # 可以增加仓位价值以满足最小保证金要求 position_value = required_position_value quantity = position_value / current_price logger.info( f" ✓ 调整仓位价值到 {position_value:.2f} USDT " f"以满足最小保证金要求 (保证金: {min_margin_usdt:.2f} USDT)" ) else: # 即使使用最大仓位也无法满足最小保证金要求 max_margin = max_position_value / actual_leverage logger.warning( f" ❌ 无法满足最小保证金要求: " f"需要 {min_margin_usdt:.2f} USDT 保证金 (仓位价值 {required_position_value:.2f} USDT)," f"但最大允许 {max_margin:.2f} USDT 保证金 (仓位价值 {max_position_value:.2f} USDT)" ) logger.warning( f" 💡 建议: 增加账户余额到至少 " f"{required_position_value / max_position_percent:.2f} USDT " f"才能满足最小保证金要求" ) return None # 检查是否通过风险控制 logger.info(f" 检查仓位大小是否符合风险控制要求...") # 计算最终的名义价值 final_notional_value = quantity * current_price # 添加最小名义价值检查(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): final_margin = (quantity * current_price) / actual_leverage logger.info( f"✓ {symbol} 仓位计算成功: {quantity:.4f} " f"(仓位价值: {position_value:.2f} USDT, " f"名义价值: {quantity * current_price:.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)) # 对做多取最大的值(最宽松),对做空取最小的值(最宽松) if side == 'BUY': 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] else: 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] # 如果提供了技术分析数据,计算技术止损(允许更紧的止损,但需要在保证金止损范围内) 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 = max(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 = min(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