a
This commit is contained in:
parent
037c33b904
commit
081deb8d62
|
|
@ -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} USDT(MIN_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
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,24 @@ const ConfigPanel = () => {
|
||||||
// 预设方案配置
|
// 预设方案配置
|
||||||
// 注意:百分比配置使用整数形式(如8.0表示8%),在应用时会转换为小数(0.08)
|
// 注意:百分比配置使用整数形式(如8.0表示8%),在应用时会转换为小数(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。系统会结合最小价格变动保护,取更宽松的一个。',
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
max_position_value = available_balance * config.TRADING_CONFIG['MAX_POSITION_PERCENT']
|
if not actual_leverage or actual_leverage <= 0:
|
||||||
min_position_value = available_balance * config.TRADING_CONFIG['MIN_POSITION_PERCENT']
|
actual_leverage = 10
|
||||||
max_position_pct = config.TRADING_CONFIG['MAX_POSITION_PERCENT'] * 100
|
margin_value = notional_value / actual_leverage
|
||||||
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" 数量: {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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user