From 081deb8d62f7554ef42589374d3658b0270e5524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sun, 18 Jan 2026 22:18:55 +0800 Subject: [PATCH] a --- backend/api/routes/config.py | 23 ++- frontend/src/components/ConfigPanel.jsx | 38 +++- trading_system/risk_manager.py | 232 +++++++++++++----------- trading_system/trade_recommender.py | 13 +- 4 files changed, 175 insertions(+), 131 deletions(-) diff --git a/backend/api/routes/config.py b/backend/api/routes/config.py index 9f70e6d..139d07c 100644 --- a/backend/api/routes/config.py +++ b/backend/api/routes/config.py @@ -92,24 +92,29 @@ async def check_config_feasibility(): worst_case_margin = None for leverage in leverage_to_check: - # 计算最小保证金要求对应的最小仓位价值 - required_position_value = min_margin_usdt * leverage + # 重要语义说明(与 trading_system 保持一致): + # - MAX/MIN_POSITION_PERCENT 表示“保证金占用比例”(不是名义价值比例) + # - MIN_MARGIN_USDT 是最小保证金(USDT) + # 同时考虑币安最小名义价值约束:名义价值>=5USDT => margin >= 5/leverage + min_notional = 5.0 + required_margin_for_notional = (min_notional / leverage) if leverage and leverage > 0 else min_notional + required_position_value = max(min_margin_usdt, required_margin_for_notional) # 实际需要的最小保证金(USDT) required_position_percent = required_position_value / available_balance if available_balance > 0 else 0 - # 计算使用最小仓位百分比时,实际能得到的保证金 + # 计算使用最小仓位百分比时,实际能得到的保证金(直接就是保证金) min_position_value = available_balance * min_position_percent - actual_min_margin = min_position_value / leverage if leverage > 0 else min_position_value + actual_min_margin = min_position_value # 检查是否可行: - # 1. 需要的仓位百分比不能超过最大允许的仓位百分比 - # 2. 使用最小仓位百分比时,实际保证金必须 >= 最小保证金要求 + # 1) 最大保证金占比是否能满足 required_position_value + # 2) 如果 MIN_POSITION_PERCENT > 0,则最小保证金占比也应 >= required_position_value(否则最小仓位配置会“阻碍”满足最小保证金/最小名义价值) condition1_ok = required_position_percent <= max_position_percent - condition2_ok = actual_min_margin >= min_margin_usdt + condition2_ok = (min_position_percent <= 0) or (actual_min_margin >= required_position_value) is_feasible_at_leverage = condition1_ok and condition2_ok leverage_results.append({ 'leverage': leverage, - 'required_position_value': required_position_value, + 'required_position_value': required_position_value, # 实际需要的最小保证金(USDT) 'required_position_percent': required_position_percent * 100, 'actual_min_margin': actual_min_margin, 'feasible': is_feasible_at_leverage, @@ -273,7 +278,7 @@ async def check_config_feasibility(): suggestions.append({ "type": "info", "title": "配置可行", - "description": f"当前配置可以正常下单。最小仓位价值: {actual_min_position_value:.2f} USDT,对应保证金: {actual_min_margin:.2f} USDT({base_leverage}x杠杆)", + "description": f"当前配置可以正常下单。最小保证金占比对应保证金: {actual_min_margin:.2f} USDT(MIN_POSITION_PERCENT={min_position_percent*100:.2f}%),最小保证金要求: {min_margin_usdt:.2f} USDT", "config_key": None, "suggested_value": None, "reason": None diff --git a/frontend/src/components/ConfigPanel.jsx b/frontend/src/components/ConfigPanel.jsx index 245cc52..9fbc604 100644 --- a/frontend/src/components/ConfigPanel.jsx +++ b/frontend/src/components/ConfigPanel.jsx @@ -22,6 +22,24 @@ const ConfigPanel = () => { // 预设方案配置 // 注意:百分比配置使用整数形式(如8.0表示8%),在应用时会转换为小数(0.08) const presets = { + swing: { + name: '波段回归(推荐)', + desc: '回归“15分钟节奏 + 精选机会 + 小保证金占用”的波段交易。建议先跑20-30单再评估。', + configs: { + // 操作频率 + SCAN_INTERVAL: 900, // 15分钟 + TOP_N_SYMBOLS: 8, + + // 仓位管理(重要语义:这些百分比均按“保证金占用比例”理解) + MAX_POSITION_PERCENT: 2.0, // 2% + MAX_TOTAL_POSITION_PERCENT: 20.0, // 20% + MIN_POSITION_PERCENT: 0.0, // 0%(等价于关闭最小仓位占比) + + // 风控 + MIN_SIGNAL_STRENGTH: 7, + USE_TRAILING_STOP: false, + }, + }, conservative: { name: '保守配置', desc: '适合新手,风险较低,止损止盈较宽松,避免被正常波动触发', @@ -581,14 +599,14 @@ const ConfigPanel = () => { {!feasibilityCheck.feasible && (

- 需要仓位价值: {feasibilityCheck.calculated_values?.required_position_percent?.toFixed(1)}% | - 最大允许: {feasibilityCheck.calculated_values?.max_allowed_position_percent?.toFixed(1)}% + 需要保证金占比: {feasibilityCheck.calculated_values?.required_position_percent?.toFixed(1)}% | + 最大允许保证金占比: {feasibilityCheck.calculated_values?.max_allowed_position_percent?.toFixed(1)}%

{feasibilityCheck.calculated_values?.actual_min_margin !== undefined && (

- 实际支持保证金: {feasibilityCheck.calculated_values.actual_min_margin.toFixed(2)} USDT - (使用最小仓位 {feasibilityCheck.calculated_values.min_position_percent?.toFixed(1)}%) | - 要求: {feasibilityCheck.current_config?.min_margin_usdt?.toFixed(2)} USDT + 最小保证金占比可提供保证金: {feasibilityCheck.calculated_values.actual_min_margin.toFixed(2)} USDT + (MIN_POSITION_PERCENT={feasibilityCheck.calculated_values.min_position_percent?.toFixed(1)}%) | + 最小保证金要求: {feasibilityCheck.current_config?.min_margin_usdt?.toFixed(2)} USDT

)}
@@ -610,8 +628,8 @@ const ConfigPanel = () => { 杠杆倍数 - 需要仓位价值 - 需要仓位% + 需要保证金(USDT) + 需要保证金% 实际最小保证金 状态 @@ -1114,9 +1132,9 @@ const getConfigDetail = (key) => { 'ENTRY_INTERVAL': '入场周期。用于精确入场的短时间周期,优化入场点,提高单笔交易的盈亏比。通常比主周期更短。建议:保守策略1h,平衡策略15m-30m,激进策略5m-15m。', // 仓位控制参数 - 'MAX_POSITION_PERCENT': '单笔最大仓位(账户余额的百分比,如0.05表示5%)。单笔交易允许的最大仓位大小。值越大单笔金额越大,潜在收益和风险都增加。注意:币安要求最小名义价值5 USDT,如果账户余额较小,系统会自动调整。建议:保守策略3-5%,平衡策略5-7%,激进策略7-10%。', - 'MAX_TOTAL_POSITION_PERCENT': '总仓位上限(账户余额的百分比,如0.30表示30%)。所有持仓的总价值不能超过账户余额的百分比,防止过度交易和风险集中。值越大可以同时持有更多仓位,但风险集中度增加。建议:保守策略20-30%,平衡策略30-40%,激进策略40-50%。', - 'MIN_POSITION_PERCENT': '单笔最小仓位(账户余额的百分比,如0.01表示1%)。单笔交易允许的最小仓位大小,避免交易过小的仓位,减少手续费影响。建议:1-2%。', + 'MAX_POSITION_PERCENT': '单笔最大保证金占用(账户余额的百分比,如0.02表示2%)。系统会先按该比例分配“保证金”,再根据杠杆换算名义价值下单。值越小越保守,适合回归波段交易、降低单笔波动对账户的伤害。注意:币安合约有最小名义价值要求(通常≥5 USDT),如果保证金过小且杠杆不够,系统会提示无法满足最小名义价值。', + 'MAX_TOTAL_POSITION_PERCENT': '总保证金上限(账户余额的百分比,如0.20表示20%)。所有持仓占用的保证金之和不能超过该上限,防止过度分散和风险集中。值越小越易控仓,便于管理持仓数量与节奏。', + 'MIN_POSITION_PERCENT': '单笔最小保证金占用(账户余额的百分比,如0.01表示1%)。用于避免过小仓位(意义不大、易受手续费影响)。如果你希望取消这一限制,可设置为0(等价于关闭最小占比)。', // 风险控制参数 'STOP_LOSS_PERCENT': '止损百分比(如0.08表示8%,相对于保证金)。当亏损达到此百分比时自动平仓止损,限制单笔交易的最大亏损。值越小止损更严格,单笔损失更小但可能被正常波动触发。值越大允许更大的回撤,但单笔损失可能较大。建议:保守策略10-15%,平衡策略8-10%,激进策略5-8%。注意:止损应该小于止盈,建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。', diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index 72f79a4..8daa989 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -31,13 +31,14 @@ class RiskManager: # 初始化ATR策略 self.atr_strategy = ATRStrategy() - async def check_position_size(self, symbol: str, quantity: float) -> bool: + async def check_position_size(self, symbol: str, quantity: float, leverage: Optional[int] = None) -> bool: """ 检查单笔仓位大小是否符合要求 Args: symbol: 交易对 quantity: 下单数量 + leverage: 杠杆倍数(用于换算保证金);若不传则使用配置的基础杠杆 Returns: 是否通过检查 @@ -53,48 +54,55 @@ class RiskManager: 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 + 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 - # 检查单笔仓位是否超过最大限制(每次都从最新配置读取) - 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 + # 重要语义: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" 仓位价值: {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}%)") + 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 position_value > max_position_value + tolerance: + if margin_value > max_margin_value + tolerance: logger.warning( - f"❌ {symbol} 单笔仓位过大: {position_value:.2f} USDT > " - f"最大限制: {max_position_value:.2f} USDT " - f"(超出: {position_value - max_position_value:.2f} USDT)" + f"❌ {symbol} 单笔保证金过大: {margin_value:.4f} USDT > " + f"最大限制: {max_margin_value:.2f} USDT " + f"(超出: {margin_value - max_margin_value:.4f} USDT)" ) return False - elif position_value > max_position_value: + elif margin_value > max_margin_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)" + f"⚠ {symbol} 保证金略超限制但 within 容差: " + f"{margin_value:.4f} USDT vs {max_margin_value:.2f} USDT " + f"(差异: {margin_value - max_margin_value:.4f} USDT)" ) - if position_value < min_position_value: + if margin_value < min_margin_value: logger.warning( - f"❌ {symbol} 单笔仓位过小: {position_value:.2f} USDT < " - f"最小限制: {min_position_value:.2f} USDT" + f"❌ {symbol} 单笔保证金过小: {margin_value:.4f} USDT < " + f"最小限制: {min_margin_value:.2f} USDT" ) return False @@ -102,11 +110,11 @@ class RiskManager: # 检查总仓位是否超过限制 logger.info(f"检查 {symbol} 总仓位限制...") - if not await self.check_total_position(position_value): + if not await self.check_total_position(margin_value): return False logger.info( - f"✓ {symbol} 所有仓位检查通过: {position_value:.2f} USDT " + f"✓ {symbol} 所有仓位检查通过: 保证金 {margin_value:.4f} USDT " f"(账户可用余额: {available_balance:.2f} USDT)" ) return True @@ -115,12 +123,12 @@ class RiskManager: logger.error(f"检查仓位大小失败 {symbol}: {e}", exc_info=True) return False - async def check_total_position(self, new_position_value: float) -> bool: + async def check_total_position(self, new_position_margin: float) -> bool: """ 检查总仓位是否超过限制 Args: - new_position_value: 新仓位价值 + new_position_margin: 新仓位保证金占用(USDT) Returns: 是否通过检查 @@ -129,22 +137,32 @@ class RiskManager: # 获取当前持仓 positions = await self.client.get_open_positions() - # 计算当前总仓位价值 + # 计算当前总保证金占用 current_position_values = [] - total_position_value = 0 + total_margin_value = 0 for pos in positions: - position_value = abs(pos['positionAmt'] * pos['entryPrice']) + 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'], - 'value': position_value, + 'notional': notional_value, + 'margin': margin_value, + 'leverage': lv, 'amount': pos['positionAmt'], 'entryPrice': pos['entryPrice'] }) - total_position_value += position_value + total_margin_value += margin_value # 加上新仓位 - total_with_new = total_position_value + new_position_value + total_with_new = total_margin_value + new_position_margin # 获取账户余额 balance = await self.client.get_account_balance() @@ -155,15 +173,15 @@ class RiskManager: 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 + 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_position:.2f} USDT ({max_total_position_pct:.1f}%)") + logger.info(f" 总保证金上限: {max_total_margin:.2f} USDT ({max_total_margin_pct:.1f}%)") logger.info(f" 当前持仓数量: {len(positions)} 个") if current_position_values: @@ -171,34 +189,34 @@ class RiskManager: 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})" + 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_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") + 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_position: + if total_with_new > max_total_margin: logger.warning("=" * 60) logger.warning( - f"❌ 总仓位超限: {total_with_new:.2f} USDT > " - f"最大限制: {max_total_position:.2f} USDT" + f"❌ 总保证金超限: {total_with_new:.4f} USDT > " + f"最大限制: {max_total_margin:.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}%)" + 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:.2f} USDT / " - f"最大限制: {max_total_position:.2f} USDT " - f"({(total_with_new / max_total_position * 100):.1f}%)" + 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 @@ -247,55 +265,68 @@ class RiskManager: 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%时,可以适当增加仓位(但不超过1.5倍) + # 涨跌幅超过5%时,可以适当增加保证金占比,但必须遵守 MAX_POSITION_PERCENT 上限 if abs(change_percent) > 5: position_percent = min( base_position_percent * 1.5, - max_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}%") - # 计算仓位价值 - position_value = available_balance * position_percent - logger.info(f" 计算仓位价值: {position_value:.2f} USDT ({position_percent*100:.1f}% of {available_balance:.2f})") + # 计算保证金与名义价值 + 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 position_value < min_notional: - logger.warning(f" ⚠ 仓位价值 {position_value:.2f} USDT < 最小名义价值 {min_notional:.2f} USDT") + if notional_value < min_notional: + logger.warning(f" ⚠ 名义价值 {notional_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)") + # 计算需要的最小保证金来满足最小名义价值: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_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") + # 检查是否可以使用更大的保证金占比(但不超过最大保证金限制) + 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_value = available_balance * max_position_percent + 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_position_percent*100:.2f}%)," - f"但最大允许 {max_allowed_value:.2f} USDT (仓位比例 {max_position_percent*100:.1f}%)" + f"需要 {min_notional:.2f} USDT (需要保证金 {required_margin:.4f} USDT)," + f"但最大允许名义 {max_allowed_notional:.2f} USDT (最大保证金 {max_allowed_margin:.2f} USDT)" ) - logger.warning(f" 💡 建议: 增加账户余额到至少 {min_notional / max_position_percent:.2f} USDT 才能满足最小名义价值要求") + logger.warning(f" 💡 建议: 提高 MAX_POSITION_PERCENT 或降低杠杆/更换币种,确保最小名义价值可满足") return None # 计算数量(考虑合约的最小数量精度) - quantity = position_value / current_price - logger.info(f" 计算数量: {quantity:.4f} (价值: {position_value:.2f} / 价格: {current_price:.4f})") + quantity = notional_value / current_price + logger.info(f" 计算数量: {quantity:.4f} (名义: {notional_value:.2f} / 价格: {current_price:.4f})") # 验证计算出的数量对应的名义价值 calculated_notional = quantity * current_price @@ -305,49 +336,40 @@ class RiskManager: 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 + notional_value = required_quantity * current_price + margin_value = notional_value / actual_leverage - # 检查最小保证金要求(如果保证金小于此值,自动调整到0.5U保证金) + # 检查最小保证金要求(margin 语义:MIN_MARGIN_USDT 本身就是保证金下限) 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) + logger.info(f" 当前保证金: {margin_value:.4f} USDT (杠杆: {actual_leverage}x)") - # 计算实际需要的保证金 = 仓位价值 / 杠杆 - 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 + if margin_value < min_margin_usdt: + # 保证金不足,需要增加保证金 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)" + f" ⚠ 保证金 {margin_value:.4f} USDT < 最小保证金要求 {min_margin_usdt:.2f} USDT" ) # 检查是否可以使用更大的仓位价值(但不超过最大仓位限制) - 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 + 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" ✓ 调整仓位价值到 {position_value:.2f} USDT " - f"以满足最小保证金要求 (保证金: {min_margin_usdt:.2f} USDT)" + f" ✓ 调整保证金到 {margin_value:.2f} USDT " + f"(名义 {notional_value:.2f} USDT) 以满足最小保证金要求" ) else: # 即使使用最大仓位也无法满足最小保证金要求 - max_margin = max_position_value / actual_leverage + max_margin = max_margin_value logger.warning( f" ❌ 无法满足最小保证金要求: " - f"需要 {min_margin_usdt:.2f} USDT 保证金 (仓位价值 {required_position_value:.2f} USDT)," - f"但最大允许 {max_margin:.2f} USDT 保证金 (仓位价值 {max_position_value:.2f} USDT)" + f"需要 {min_margin_usdt:.2f} USDT 保证金," + f"但最大允许 {max_margin:.2f} USDT 保证金 (MAX_POSITION_PERCENT={max_position_percent*100:.2f}%)" ) logger.warning( f" 💡 建议: 增加账户余额到至少 " - f"{required_position_value / max_position_percent:.2f} USDT " + f"{min_margin_usdt / max_position_percent:.2f} USDT " f"才能满足最小保证金要求" ) return None @@ -355,8 +377,9 @@ class RiskManager: # 检查是否通过风险控制 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 @@ -367,12 +390,11 @@ class RiskManager: logger.warning(f" 💡 此类小单子意义不大,拒绝开仓") return None - if await self.check_position_size(symbol, quantity): - final_margin = (quantity * current_price) / actual_leverage + if await self.check_position_size(symbol, quantity, leverage=actual_leverage): logger.info( f"✓ {symbol} 仓位计算成功: {quantity:.4f} " - f"(仓位价值: {position_value:.2f} USDT, " - f"名义价值: {quantity * current_price:.2f} USDT, " + f"(保证金: {final_margin:.4f} USDT, " + f"名义价值: {final_notional_value:.2f} USDT, " f"保证金: {final_margin:.4f} USDT, 杠杆: {actual_leverage}x)" ) return quantity diff --git a/trading_system/trade_recommender.py b/trading_system/trade_recommender.py index 63c0339..a98fb1d 100644 --- a/trading_system/trade_recommender.py +++ b/trading_system/trade_recommender.py @@ -362,14 +362,15 @@ class TradeRecommender: entry_price = current_price # 估算仓位数量和杠杆(用于计算止损止盈) - # 使用建议的仓位比例和账户余额来估算 + # 重要语义:suggested_position_pct 表示“保证金占用比例” account_balance = symbol_info.get('account_balance', 1000) suggested_position_pct = symbol_info.get('suggested_position_pct', 0.05) leverage = config.TRADING_CONFIG.get('LEVERAGE', 10) - # 估算仓位价值 - estimated_position_value = account_balance * suggested_position_pct - estimated_quantity = estimated_position_value / entry_price if entry_price > 0 else 0 + # 估算保证金与名义价值 + estimated_margin = account_balance * suggested_position_pct + estimated_notional = estimated_margin * leverage if leverage and leverage > 0 else estimated_margin + estimated_quantity = estimated_notional / entry_price if entry_price > 0 else 0 # 计算基于保证金的止损止盈 stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08) @@ -387,7 +388,6 @@ class TradeRecommender: ) # 计算止损百分比(相对于保证金,用于显示) - estimated_margin = estimated_position_value / leverage if leverage > 0 else estimated_position_value stop_loss_amount = estimated_margin * stop_loss_pct_margin if direction == 'BUY': stop_loss_pct = (entry_price - stop_loss_price) / entry_price @@ -444,8 +444,7 @@ class TradeRecommender: estimated_win_rate = max(35, min(75, base_win_rate)) # 计算盈亏USDT评估(基于保证金) - position_value = account_balance * suggested_position_pct - margin = position_value / leverage if leverage > 0 else position_value + margin = account_balance * suggested_position_pct # 止损止盈金额(相对于保证金) stop_loss_usdt = margin * stop_loss_pct_margin