This commit is contained in:
薇薇安 2026-01-18 22:18:55 +08:00
parent 037c33b904
commit 081deb8d62
4 changed files with 175 additions and 131 deletions

View File

@ -92,24 +92,29 @@ async def check_config_feasibility():
worst_case_margin = None worst_case_margin = None
for leverage in leverage_to_check: for leverage in leverage_to_check:
# 计算最小保证金要求对应的最小仓位价值 # 重要语义说明(与 trading_system 保持一致):
required_position_value = min_margin_usdt * leverage # - 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 required_position_percent = required_position_value / available_balance if available_balance > 0 else 0
# 计算使用最小仓位百分比时,实际能得到的保证金 # 计算使用最小仓位百分比时,实际能得到的保证金(直接就是保证金)
min_position_value = available_balance * min_position_percent 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. 需要的仓位百分比不能超过最大允许的仓位百分比 # 1) 最大保证金占比是否能满足 required_position_value
# 2. 使用最小仓位百分比时,实际保证金必须 >= 最小保证金要求 # 2) 如果 MIN_POSITION_PERCENT > 0则最小保证金占比也应 >= required_position_value否则最小仓位配置会“阻碍”满足最小保证金/最小名义价值)
condition1_ok = required_position_percent <= max_position_percent 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 is_feasible_at_leverage = condition1_ok and condition2_ok
leverage_results.append({ leverage_results.append({
'leverage': leverage, 'leverage': leverage,
'required_position_value': required_position_value, 'required_position_value': required_position_value, # 实际需要的最小保证金USDT
'required_position_percent': required_position_percent * 100, 'required_position_percent': required_position_percent * 100,
'actual_min_margin': actual_min_margin, 'actual_min_margin': actual_min_margin,
'feasible': is_feasible_at_leverage, 'feasible': is_feasible_at_leverage,
@ -273,7 +278,7 @@ async def check_config_feasibility():
suggestions.append({ suggestions.append({
"type": "info", "type": "info",
"title": "配置可行", "title": "配置可行",
"description": f"当前配置可以正常下单。最小仓位价值: {actual_min_position_value:.2f} USDT对应保证金: {actual_min_margin:.2f} USDT{base_leverage}x杠杆", "description": f"当前配置可以正常下单。最小保证金占比对应保证金: {actual_min_margin:.2f} USDTMIN_POSITION_PERCENT={min_position_percent*100:.2f}%),最小保证金要求: {min_margin_usdt:.2f} USDT",
"config_key": None, "config_key": None,
"suggested_value": None, "suggested_value": None,
"reason": None "reason": None

View File

@ -22,6 +22,24 @@ const ConfigPanel = () => {
// //
// 使8.08%0.08 // 使8.08%0.08
const presets = { 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: { conservative: {
name: '保守配置', name: '保守配置',
desc: '适合新手,风险较低,止损止盈较宽松,避免被正常波动触发', desc: '适合新手,风险较低,止损止盈较宽松,避免被正常波动触发',
@ -581,14 +599,14 @@ const ConfigPanel = () => {
{!feasibilityCheck.feasible && ( {!feasibilityCheck.feasible && (
<div className="warning-text"> <div className="warning-text">
<p> <p>
需要仓位价值: <strong>{feasibilityCheck.calculated_values?.required_position_percent?.toFixed(1)}%</strong> | 需要保证金占比: <strong>{feasibilityCheck.calculated_values?.required_position_percent?.toFixed(1)}%</strong> |
最大允许: <strong>{feasibilityCheck.calculated_values?.max_allowed_position_percent?.toFixed(1)}%</strong> 最大允许保证金占比: <strong>{feasibilityCheck.calculated_values?.max_allowed_position_percent?.toFixed(1)}%</strong>
</p> </p>
{feasibilityCheck.calculated_values?.actual_min_margin !== undefined && ( {feasibilityCheck.calculated_values?.actual_min_margin !== undefined && (
<p> <p>
实际支持保证金: <strong>{feasibilityCheck.calculated_values.actual_min_margin.toFixed(2)} USDT</strong> 最小保证金占比可提供保证金: <strong>{feasibilityCheck.calculated_values.actual_min_margin.toFixed(2)} USDT</strong>
(使用最小仓位 {feasibilityCheck.calculated_values.min_position_percent?.toFixed(1)}%) | (MIN_POSITION_PERCENT={feasibilityCheck.calculated_values.min_position_percent?.toFixed(1)}%) |
要求: <strong>{feasibilityCheck.current_config?.min_margin_usdt?.toFixed(2)} USDT</strong> 最小保证金要求: <strong>{feasibilityCheck.current_config?.min_margin_usdt?.toFixed(2)} USDT</strong>
</p> </p>
)} )}
</div> </div>
@ -610,8 +628,8 @@ const ConfigPanel = () => {
<thead> <thead>
<tr> <tr>
<th>杠杆倍数</th> <th>杠杆倍数</th>
<th>需要仓位价值</th> <th>需要保证金(USDT)</th>
<th>需要仓位%</th> <th>需要保证金%</th>
<th>实际最小保证金</th> <th>实际最小保证金</th>
<th>状态</th> <th>状态</th>
</tr> </tr>
@ -1114,9 +1132,9 @@ const getConfigDetail = (key) => {
'ENTRY_INTERVAL': '入场周期。用于精确入场的短时间周期优化入场点提高单笔交易的盈亏比。通常比主周期更短。建议保守策略1h平衡策略15m-30m激进策略5m-15m。', 'ENTRY_INTERVAL': '入场周期。用于精确入场的短时间周期优化入场点提高单笔交易的盈亏比。通常比主周期更短。建议保守策略1h平衡策略15m-30m激进策略5m-15m。',
// //
'MAX_POSITION_PERCENT': '单笔最大仓位账户余额的百分比如0.05表示5%。单笔交易允许的最大仓位大小。值越大单笔金额越大潜在收益和风险都增加。注意币安要求最小名义价值5 USDT如果账户余额较小系统会自动调整。建议保守策略3-5%平衡策略5-7%激进策略7-10%。', 'MAX_POSITION_PERCENT': '单笔最大保证金占用账户余额的百分比如0.02表示2%。系统会先按该比例分配“保证金”再根据杠杆换算名义价值下单。值越小越保守适合回归波段交易、降低单笔波动对账户的伤害。注意币安合约有最小名义价值要求通常≥5 USDT如果保证金过小且杠杆不够系统会提示无法满足最小名义价值。',
'MAX_TOTAL_POSITION_PERCENT': '总仓位上限账户余额的百分比如0.30表示30%。所有持仓的总价值不能超过账户余额的百分比防止过度交易和风险集中。值越大可以同时持有更多仓位但风险集中度增加。建议保守策略20-30%平衡策略30-40%激进策略40-50%。', 'MAX_TOTAL_POSITION_PERCENT': '总保证金上限账户余额的百分比如0.20表示20%)。所有持仓占用的保证金之和不能超过该上限,防止过度分散和风险集中。值越小越易控仓,便于管理持仓数量与节奏。',
'MIN_POSITION_PERCENT': '单笔最小仓位账户余额的百分比如0.01表示1%。单笔交易允许的最小仓位大小避免交易过小的仓位减少手续费影响。建议1-2%。', 'MIN_POSITION_PERCENT': '单笔最小保证金占用账户余额的百分比如0.01表示1%。用于避免过小仓位意义不大、易受手续费影响。如果你希望取消这一限制可设置为0等价于关闭最小占比。',
// //
'STOP_LOSS_PERCENT': '止损百分比如0.08表示8%相对于保证金。当亏损达到此百分比时自动平仓止损限制单笔交易的最大亏损。值越小止损更严格单笔损失更小但可能被正常波动触发。值越大允许更大的回撤但单笔损失可能较大。建议保守策略10-15%平衡策略8-10%激进策略5-8%。注意止损应该小于止盈建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。', 'STOP_LOSS_PERCENT': '止损百分比如0.08表示8%相对于保证金。当亏损达到此百分比时自动平仓止损限制单笔交易的最大亏损。值越小止损更严格单笔损失更小但可能被正常波动触发。值越大允许更大的回撤但单笔损失可能较大。建议保守策略10-15%平衡策略8-10%激进策略5-8%。注意止损应该小于止盈建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。',

View File

@ -31,13 +31,14 @@ class RiskManager:
# 初始化ATR策略 # 初始化ATR策略
self.atr_strategy = ATRStrategy() 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: Args:
symbol: 交易对 symbol: 交易对
quantity: 下单数量 quantity: 下单数量
leverage: 杠杆倍数用于换算保证金若不传则使用配置的基础杠杆
Returns: Returns:
是否通过检查 是否通过检查
@ -53,48 +54,55 @@ class RiskManager:
logger.warning(f"{symbol} 账户可用余额不足: {available_balance:.2f} USDT") logger.warning(f"{symbol} 账户可用余额不足: {available_balance:.2f} USDT")
return False return False
# 计算仓位价值(假设使用当前价格 # 计算名义价值与保证金(名义价值/杠杆
ticker = await self.client.get_ticker_24h(symbol) ticker = await self.client.get_ticker_24h(symbol)
if not ticker: if not ticker:
logger.warning(f"{symbol} 无法获取价格数据") logger.warning(f"{symbol} 无法获取价格数据")
return False return False
current_price = ticker['price'] 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
# 检查单笔仓位是否超过最大限制(每次都从最新配置读取) # 重要语义POSITION_PERCENT 均按“保证金占用比例”计算(更符合 stop_loss/take_profit 的 margin 逻辑
max_position_value = available_balance * config.TRADING_CONFIG['MAX_POSITION_PERCENT'] max_margin_value = available_balance * config.TRADING_CONFIG['MAX_POSITION_PERCENT']
min_position_value = available_balance * config.TRADING_CONFIG['MIN_POSITION_PERCENT'] min_margin_value = available_balance * config.TRADING_CONFIG['MIN_POSITION_PERCENT']
max_position_pct = config.TRADING_CONFIG['MAX_POSITION_PERCENT'] * 100 max_margin_pct = config.TRADING_CONFIG['MAX_POSITION_PERCENT'] * 100
min_position_pct = config.TRADING_CONFIG['MIN_POSITION_PERCENT'] * 100 min_margin_pct = config.TRADING_CONFIG['MIN_POSITION_PERCENT'] * 100
logger.info(f" 数量: {quantity:.4f}") logger.info(f" 数量: {quantity:.4f}")
logger.info(f" 价格: {current_price:.4f} USDT") logger.info(f" 价格: {current_price:.4f} USDT")
logger.info(f" 仓位价值: {position_value:.2f} USDT") logger.info(f" 名义价值: {notional_value:.2f} USDT")
logger.info(f" 单笔最大限制: {max_position_value:.2f} USDT ({max_position_pct:.1f}%)") logger.info(f" 杠杆: {actual_leverage}x")
logger.info(f" 单笔最小限制: {min_position_value:.2f} USDT ({min_position_pct:.1f}%)") 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 # 使用小的容差来处理浮点数精度问题0.01 USDT
tolerance = 0.01 tolerance = 0.01
if position_value > max_position_value + tolerance: if margin_value > max_margin_value + tolerance:
logger.warning( logger.warning(
f"{symbol} 单笔仓位过大: {position_value:.2f} USDT > " f"{symbol} 单笔保证金过大: {margin_value:.4f} USDT > "
f"最大限制: {max_position_value:.2f} USDT " f"最大限制: {max_margin_value:.2f} USDT "
f"(超出: {position_value - max_position_value:.2f} USDT)" f"(超出: {margin_value - max_margin_value:.4f} USDT)"
) )
return False return False
elif position_value > max_position_value: elif margin_value > max_margin_value:
# 在容差范围内,允许通过(浮点数精度问题) # 在容差范围内,允许通过(浮点数精度问题)
logger.info( logger.info(
f"{symbol} 仓位价值略超限制但 within 容差: " f"{symbol} 保证金略超限制但 within 容差: "
f"{position_value:.2f} USDT vs {max_position_value:.2f} USDT " f"{margin_value:.4f} USDT vs {max_margin_value:.2f} USDT "
f"(差异: {position_value - max_position_value:.4f} USDT)" f"(差异: {margin_value - max_margin_value:.4f} USDT)"
) )
if position_value < min_position_value: if margin_value < min_margin_value:
logger.warning( logger.warning(
f"{symbol} 单笔仓位过小: {position_value:.2f} USDT < " f"{symbol} 单笔保证金过小: {margin_value:.4f} USDT < "
f"最小限制: {min_position_value:.2f} USDT" f"最小限制: {min_margin_value:.2f} USDT"
) )
return False return False
@ -102,11 +110,11 @@ class RiskManager:
# 检查总仓位是否超过限制 # 检查总仓位是否超过限制
logger.info(f"检查 {symbol} 总仓位限制...") logger.info(f"检查 {symbol} 总仓位限制...")
if not await self.check_total_position(position_value): if not await self.check_total_position(margin_value):
return False return False
logger.info( logger.info(
f"{symbol} 所有仓位检查通过: {position_value:.2f} USDT " f"{symbol} 所有仓位检查通过: 保证金 {margin_value:.4f} USDT "
f"(账户可用余额: {available_balance:.2f} USDT)" f"(账户可用余额: {available_balance:.2f} USDT)"
) )
return True return True
@ -115,12 +123,12 @@ class RiskManager:
logger.error(f"检查仓位大小失败 {symbol}: {e}", exc_info=True) logger.error(f"检查仓位大小失败 {symbol}: {e}", exc_info=True)
return False 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: Args:
new_position_value: 新仓位价值 new_position_margin: 新仓位保证金占用USDT
Returns: Returns:
是否通过检查 是否通过检查
@ -129,22 +137,32 @@ class RiskManager:
# 获取当前持仓 # 获取当前持仓
positions = await self.client.get_open_positions() positions = await self.client.get_open_positions()
# 计算当前总仓位价值 # 计算当前总保证金占用
current_position_values = [] current_position_values = []
total_position_value = 0 total_margin_value = 0
for pos in positions: 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({ current_position_values.append({
'symbol': pos['symbol'], 'symbol': pos['symbol'],
'value': position_value, 'notional': notional_value,
'margin': margin_value,
'leverage': lv,
'amount': pos['positionAmt'], 'amount': pos['positionAmt'],
'entryPrice': pos['entryPrice'] '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() balance = await self.client.get_account_balance()
@ -155,15 +173,15 @@ class RiskManager:
logger.warning("账户总余额为0无法开仓") logger.warning("账户总余额为0无法开仓")
return False return False
max_total_position = total_balance * config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT'] max_total_margin = total_balance * config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT']
max_total_position_pct = config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT'] * 100 max_total_margin_pct = config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT'] * 100
# 详细日志 # 详细日志
logger.info("=" * 60) logger.info("=" * 60)
logger.info("总仓位检查详情:") logger.info("总仓位检查详情:")
logger.info(f" 账户总余额: {total_balance:.2f} USDT") logger.info(f" 账户总余额: {total_balance:.2f} USDT")
logger.info(f" 账户可用余额: {available_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)}") logger.info(f" 当前持仓数量: {len(positions)}")
if current_position_values: if current_position_values:
@ -171,34 +189,34 @@ class RiskManager:
for pos_info in current_position_values: for pos_info in current_position_values:
logger.info( logger.info(
f" - {pos_info['symbol']}: " f" - {pos_info['symbol']}: "
f"{pos_info['value']:.2f} USDT " f"保证金 {pos_info['margin']:.4f} USDT "
f"(数量: {pos_info['amount']:.4f}, " f"(名义 {pos_info['notional']:.2f} USDT, {pos_info['leverage']}x, "
f"入场价: {pos_info['entryPrice']:.4f})" f"数量: {pos_info['amount']:.4f}, 入场价: {pos_info['entryPrice']:.4f})"
) )
logger.info(f" 当前总仓位: {total_position_value:.2f} USDT") logger.info(f" 当前总保证金: {total_margin_value:.4f} USDT")
logger.info(f" 新仓位价值: {new_position_value:.2f} USDT") logger.info(f" 新仓位保证金: {new_position_margin:.4f} USDT")
logger.info(f" 开仓后总仓位: {total_with_new:.2f} USDT") logger.info(f" 开仓后总保证金: {total_with_new:.4f} USDT")
logger.info(f" 剩余可用仓位: {max_total_position - total_position_value:.2f} 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("=" * 60)
logger.warning( logger.warning(
f"❌ 总仓位超限: {total_with_new:.2f} USDT > " f"❌ 总保证金超限: {total_with_new:.4f} USDT > "
f"最大限制: {max_total_position:.2f} USDT" f"最大限制: {max_total_margin:.2f} USDT"
) )
logger.warning( logger.warning(
f" 超出: {total_with_new - max_total_position:.2f} USDT " f" 超出: {total_with_new - max_total_margin:.4f} USDT "
f"({((total_with_new - max_total_position) / max_total_position * 100):.1f}%)" f"({((total_with_new - max_total_margin) / max_total_margin * 100):.1f}%)"
) )
logger.warning(" 建议: 平掉部分持仓或等待现有持仓平仓后再开新仓") logger.warning(" 建议: 平掉部分持仓或等待现有持仓平仓后再开新仓")
logger.warning("=" * 60) logger.warning("=" * 60)
return False return False
logger.info( logger.info(
f"✓ 总仓位检查通过: {total_with_new:.2f} USDT / " f"✓ 总保证金检查通过: {total_with_new:.4f} USDT / "
f"最大限制: {max_total_position:.2f} USDT " f"最大限制: {max_total_margin:.2f} USDT "
f"({(total_with_new / max_total_position * 100):.1f}%)" f"({(total_with_new / max_total_margin * 100):.1f}%)"
) )
logger.info("=" * 60) logger.info("=" * 60)
return True return True
@ -247,55 +265,68 @@ class RiskManager:
current_price = ticker['price'] current_price = ticker['price']
logger.info(f" 当前价格: {current_price:.4f} USDT") 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'] base_position_percent = config.TRADING_CONFIG['MAX_POSITION_PERCENT']
max_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'] min_position_percent = config.TRADING_CONFIG['MIN_POSITION_PERCENT']
# 涨跌幅超过5%时可以适当增加仓位但不超过1.5倍) # 涨跌幅超过5%时,可以适当增加保证金占比,但必须遵守 MAX_POSITION_PERCENT 上限
if abs(change_percent) > 5: if abs(change_percent) > 5:
position_percent = min( position_percent = min(
base_position_percent * 1.5, base_position_percent * 1.5,
max_position_percent * 1.5 max_position_percent
) )
logger.info(f" 涨跌幅 {change_percent:.2f}% > 5%,使用增强仓位比例: {position_percent*100:.1f}%") logger.info(f" 涨跌幅 {change_percent:.2f}% > 5%,使用增强仓位比例: {position_percent*100:.1f}%")
else: else:
position_percent = base_position_percent position_percent = base_position_percent
logger.info(f" 涨跌幅 {change_percent:.2f}%,使用标准仓位比例: {position_percent*100:.1f}%") logger.info(f" 涨跌幅 {change_percent:.2f}%,使用标准仓位比例: {position_percent*100:.1f}%")
# 计算仓位价值 # 计算保证金与名义价值
position_value = available_balance * position_percent margin_value = available_balance * position_percent
logger.info(f" 计算仓位价值: {position_value:.2f} USDT ({position_percent*100:.1f}% of {available_balance:.2f})") 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 # 确保仓位价值满足最小名义价值要求币安要求至少5 USDT
min_notional = 5.0 # 币安合约最小名义价值 min_notional = 5.0 # 币安合约最小名义价值
if position_value < min_notional: if notional_value < min_notional:
logger.warning(f"仓位价值 {position_value:.2f} USDT < 最小名义价值 {min_notional:.2f} USDT") logger.warning(f"名义价值 {notional_value:.2f} USDT < 最小名义价值 {min_notional:.2f} USDT")
# 计算需要的最小仓位比例来满足最小名义价值 # 计算需要的最小保证金来满足最小名义价值margin >= min_notional / leverage
required_position_percent = min_notional / available_balance required_margin = min_notional / actual_leverage
logger.info(f" 需要的最小仓位比例: {required_position_percent*100:.2f}% (最小名义价值 {min_notional:.2f} USDT / 可用余额 {available_balance:.2f} USDT)") 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: if required_margin_percent <= max_position_percent:
# 可以使用更大的仓位比例来满足最小名义价值 position_percent = required_margin_percent
position_percent = required_position_percent margin_value = required_margin
position_value = min_notional notional_value = min_notional
logger.info(f" ✓ 调整仓位比例{position_percent*100:.2f}% 以满足最小名义价值: {position_value:.2f} USDT") logger.info(f" ✓ 调整保证金占比{position_percent*100:.2f}% 以满足最小名义价值: {notional_value:.2f} USDT")
else: 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( logger.warning(
f" ❌ 无法满足最小名义价值要求: " f" ❌ 无法满足最小名义价值要求: "
f"需要 {min_notional:.2f} USDT (仓位比例 {required_position_percent*100:.2f}%)" f"需要 {min_notional:.2f} USDT (需要保证金 {required_margin:.4f} USDT)"
f"但最大允许 {max_allowed_value:.2f} USDT (仓位比例 {max_position_percent*100:.1f}%)" 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 return None
# 计算数量(考虑合约的最小数量精度) # 计算数量(考虑合约的最小数量精度)
quantity = position_value / current_price quantity = notional_value / current_price
logger.info(f" 计算数量: {quantity:.4f} (价值: {position_value:.2f} / 价格: {current_price:.4f})") logger.info(f" 计算数量: {quantity:.4f} (名义: {notional_value:.2f} / 价格: {current_price:.4f})")
# 验证计算出的数量对应的名义价值 # 验证计算出的数量对应的名义价值
calculated_notional = quantity * current_price calculated_notional = quantity * current_price
@ -305,49 +336,40 @@ class RiskManager:
logger.warning(f" ⚠ 计算出的名义价值 {calculated_notional:.2f} USDT < {min_notional:.2f} USDT") logger.warning(f" ⚠ 计算出的名义价值 {calculated_notional:.2f} USDT < {min_notional:.2f} USDT")
logger.info(f" ✓ 调整数量从 {quantity:.4f}{required_quantity:.4f}") logger.info(f" ✓ 调整数量从 {quantity:.4f}{required_quantity:.4f}")
quantity = required_quantity 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 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)")
actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10)
# 计算实际需要的保证金 = 仓位价值 / 杠杆 if margin_value < min_margin_usdt:
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( logger.warning(
f" ⚠ 保证金 {required_margin:.4f} USDT < 最小保证金要求 {min_margin_usdt:.2f} USDT" f" ⚠ 保证金 {margin_value:.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 max_margin_value = available_balance * max_position_percent
if required_position_value <= max_position_value: if min_margin_usdt <= max_margin_value:
# 可以增加仓位价值以满足最小保证金要求 margin_value = min_margin_usdt
position_value = required_position_value notional_value = margin_value * actual_leverage
quantity = position_value / current_price quantity = notional_value / current_price
logger.info( logger.info(
f" ✓ 调整仓位价值到 {position_value:.2f} USDT " f" ✓ 调整保证金到 {margin_value:.2f} USDT "
f"以满足最小保证金要求 (保证金: {min_margin_usdt:.2f} USDT)" f"(名义 {notional_value:.2f} USDT) 以满足最小保证金要求"
) )
else: else:
# 即使使用最大仓位也无法满足最小保证金要求 # 即使使用最大仓位也无法满足最小保证金要求
max_margin = max_position_value / actual_leverage max_margin = max_margin_value
logger.warning( logger.warning(
f" ❌ 无法满足最小保证金要求: " f" ❌ 无法满足最小保证金要求: "
f"需要 {min_margin_usdt:.2f} USDT 保证金 (仓位价值 {required_position_value:.2f} USDT)" f"需要 {min_margin_usdt:.2f} USDT 保证金"
f"但最大允许 {max_margin:.2f} USDT 保证金 (仓位价值 {max_position_value:.2f} USDT)" f"但最大允许 {max_margin:.2f} USDT 保证金 (MAX_POSITION_PERCENT={max_position_percent*100:.2f}%)"
) )
logger.warning( logger.warning(
f" 💡 建议: 增加账户余额到至少 " f" 💡 建议: 增加账户余额到至少 "
f"{required_position_value / max_position_percent:.2f} USDT " f"{min_margin_usdt / max_position_percent:.2f} USDT "
f"才能满足最小保证金要求" f"才能满足最小保证金要求"
) )
return None return None
@ -355,8 +377,9 @@ class RiskManager:
# 检查是否通过风险控制 # 检查是否通过风险控制
logger.info(f" 检查仓位大小是否符合风险控制要求...") logger.info(f" 检查仓位大小是否符合风险控制要求...")
# 计算最终的名义价值 # 计算最终的名义价值与保证金
final_notional_value = quantity * current_price final_notional_value = quantity * current_price
final_margin = final_notional_value / actual_leverage if actual_leverage > 0 else final_notional_value
# 添加最小名义价值检查0.2 USDT避免下无意义的小单子 # 添加最小名义价值检查0.2 USDT避免下无意义的小单子
MIN_NOTIONAL_VALUE = 0.2 # 最小名义价值0.2 USDT MIN_NOTIONAL_VALUE = 0.2 # 最小名义价值0.2 USDT
@ -367,12 +390,11 @@ class RiskManager:
logger.warning(f" 💡 此类小单子意义不大,拒绝开仓") logger.warning(f" 💡 此类小单子意义不大,拒绝开仓")
return None return None
if await self.check_position_size(symbol, quantity): if await self.check_position_size(symbol, quantity, leverage=actual_leverage):
final_margin = (quantity * current_price) / actual_leverage
logger.info( logger.info(
f"{symbol} 仓位计算成功: {quantity:.4f} " f"{symbol} 仓位计算成功: {quantity:.4f} "
f"(仓位价值: {position_value:.2f} USDT, " f"(保证金: {final_margin:.4f} USDT, "
f"名义价值: {quantity * current_price:.2f} USDT, " f"名义价值: {final_notional_value:.2f} USDT, "
f"保证金: {final_margin:.4f} USDT, 杠杆: {actual_leverage}x)" f"保证金: {final_margin:.4f} USDT, 杠杆: {actual_leverage}x)"
) )
return quantity return quantity

View File

@ -362,14 +362,15 @@ class TradeRecommender:
entry_price = current_price entry_price = current_price
# 估算仓位数量和杠杆(用于计算止损止盈) # 估算仓位数量和杠杆(用于计算止损止盈)
# 使用建议的仓位比例和账户余额来估算 # 重要语义suggested_position_pct 表示“保证金占用比例”
account_balance = symbol_info.get('account_balance', 1000) account_balance = symbol_info.get('account_balance', 1000)
suggested_position_pct = symbol_info.get('suggested_position_pct', 0.05) suggested_position_pct = symbol_info.get('suggested_position_pct', 0.05)
leverage = config.TRADING_CONFIG.get('LEVERAGE', 10) leverage = config.TRADING_CONFIG.get('LEVERAGE', 10)
# 估算仓位价值 # 估算保证金与名义价值
estimated_position_value = account_balance * suggested_position_pct estimated_margin = account_balance * suggested_position_pct
estimated_quantity = estimated_position_value / entry_price if entry_price > 0 else 0 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) 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 stop_loss_amount = estimated_margin * stop_loss_pct_margin
if direction == 'BUY': if direction == 'BUY':
stop_loss_pct = (entry_price - stop_loss_price) / entry_price 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)) estimated_win_rate = max(35, min(75, base_win_rate))
# 计算盈亏USDT评估基于保证金 # 计算盈亏USDT评估基于保证金
position_value = account_balance * suggested_position_pct margin = account_balance * suggested_position_pct
margin = position_value / leverage if leverage > 0 else position_value
# 止损止盈金额(相对于保证金) # 止损止盈金额(相对于保证金)
stop_loss_usdt = margin * stop_loss_pct_margin stop_loss_usdt = margin * stop_loss_pct_margin