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