This commit is contained in:
薇薇安 2026-01-19 00:22:50 +08:00
parent bec62631cb
commit 6394701732
3 changed files with 80 additions and 16 deletions

View File

@ -135,7 +135,10 @@ async def get_realtime_account_data():
# 计算总仓位价值和总盈亏 # 计算总仓位价值和总盈亏
logger.info("步骤7: 计算仓位统计...") logger.info("步骤7: 计算仓位统计...")
# total_position_value历史上这里代表“名义仓位价值(notional)”(按标记价)
total_position_value = 0 total_position_value = 0
# total_margin_value更贴近风控配置语义保证金占用
total_margin_value = 0
total_pnl = 0 total_pnl = 0
open_positions_count = 0 open_positions_count = 0
@ -154,13 +157,23 @@ async def get_realtime_account_data():
position_value = abs(position_amt * mark_price) position_value = abs(position_amt * mark_price)
total_position_value += position_value total_position_value += position_value
# 保证金占用(粗略口径):名义/杠杆(币安页面的展示会更复杂,但这个口径与 MAX_TOTAL_POSITION_PERCENT 对齐)
try:
lv = float(pos.get('leverage', 0) or 0)
if lv <= 0:
lv = 1.0
except Exception:
lv = 1.0
total_margin_value += (position_value / lv)
total_pnl += unrealized_pnl total_pnl += unrealized_pnl
open_positions_count += 1 open_positions_count += 1
logger.debug(f" - {pos.get('symbol')}: 价值={position_value:.2f}, 盈亏={unrealized_pnl:.2f}") logger.debug(f" - {pos.get('symbol')}: 价值={position_value:.2f}, 盈亏={unrealized_pnl:.2f}")
logger.info(" ✓ 仓位统计计算完成") logger.info(" ✓ 仓位统计计算完成")
logger.info(f" - 总仓位价值: {total_position_value:.2f} USDT") logger.info(f" - 总名义仓位: {total_position_value:.2f} USDT")
logger.info(f" - 总保证金占用(估算): {total_margin_value:.2f} USDT")
logger.info(f" - 总盈亏: {total_pnl:.2f} USDT") logger.info(f" - 总盈亏: {total_pnl:.2f} USDT")
logger.info(f" - 持仓数量: {open_positions_count}") logger.info(f" - 持仓数量: {open_positions_count}")
@ -176,7 +189,10 @@ async def get_realtime_account_data():
result = { result = {
"total_balance": balance.get('total', 0) if balance else 0, "total_balance": balance.get('total', 0) if balance else 0,
"available_balance": balance.get('available', 0) if balance else 0, "available_balance": balance.get('available', 0) if balance else 0,
# 名义仓位(按标记价汇总)
"total_position_value": total_position_value, "total_position_value": total_position_value,
# 保证金占用(名义/杠杆汇总)
"total_margin_value": total_margin_value,
"total_pnl": total_pnl, "total_pnl": total_pnl,
"open_positions": open_positions_count "open_positions": open_positions_count
} }

View File

@ -176,21 +176,57 @@ async def get_dashboard_data():
try: try:
from database.models import TradingConfig from database.models import TradingConfig
total_balance = float(account_data.get('total_balance', 0)) total_balance = float(account_data.get('total_balance', 0))
total_position_value = float(account_data.get('total_position_value', 0))
max_total_position_percent = float(TradingConfig.get_value('MAX_TOTAL_POSITION_PERCENT', 0.30)) max_total_position_percent = float(TradingConfig.get_value('MAX_TOTAL_POSITION_PERCENT', 0.30))
# 当前仓位占比 # 名义仓位notional与保证金占用margin是两个口径
current_position_percent = (total_position_value / total_balance * 100) if total_balance > 0 else 0 # - 名义仓位可以 > 100%(高杠杆下非常正常)
# - MAX_TOTAL_POSITION_PERCENT 在当前系统语义里是“保证金占用比例”
total_notional_value = float(account_data.get('total_position_value', 0))
# 最大仓位量(根据配置的最大占比计算) # 优先使用 account_data 里的 total_margin_value如果没有则从 open_trades 汇总兜底
max_position_value = total_balance * max_total_position_percent total_margin_value = account_data.get('total_margin_value', None)
try:
total_margin_value = float(total_margin_value) if total_margin_value is not None else None
except Exception:
total_margin_value = None
if total_margin_value is None:
total_margin_value = 0.0
for t in open_trades or []:
try:
mv = t.get("margin_usdt", None)
if mv is None:
# fallback名义/杠杆
nv = float(t.get("notional_usdt", 0) or 0)
lv = float(t.get("leverage", 0) or 0)
if lv <= 0:
lv = 1.0
mv = nv / lv
total_margin_value += float(mv or 0)
except Exception:
continue
# 当前仓位占比(保证金口径,与你的 MAX_TOTAL_POSITION_PERCENT 对齐)
current_margin_percent = (total_margin_value / total_balance * 100) if total_balance > 0 else 0
# 名义占比(仅用于参考)
current_notional_percent = (total_notional_value / total_balance * 100) if total_balance > 0 else 0
# 最大允许保证金USDT
max_margin_value = total_balance * max_total_position_percent
position_stats = { position_stats = {
"current_position_percent": round(current_position_percent, 2), # 兼容旧字段current_position_percent 现在代表“保证金占比”
"current_position_percent": round(current_margin_percent, 2),
"current_position_percent_type": "margin",
"current_notional_percent": round(current_notional_percent, 2),
"max_position_percent": round(max_total_position_percent * 100, 2), "max_position_percent": round(max_total_position_percent * 100, 2),
"max_position_value": round(max_position_value, 2), # 兼容旧字段max_position_value/total_position_value 现在代表“保证金(USDT)”
"max_position_value": round(max_margin_value, 2),
"total_balance": round(total_balance, 2), "total_balance": round(total_balance, 2),
"total_position_value": round(total_position_value, 2) "total_position_value": round(total_margin_value, 2),
# 额外信息名义价值USDT用于解释“名义占比可能>100%”
"total_notional_value": round(total_notional_value, 2),
"total_margin_value": round(total_margin_value, 2),
} }
except Exception as e: except Exception as e:
logger.warning(f"计算仓位占比信息失败: {e}") logger.warning(f"计算仓位占比信息失败: {e}")

View File

@ -165,9 +165,15 @@ const StatsDashboard = () => {
<span className="value">{parseFloat(account.available_balance).toFixed(2)} USDT</span> <span className="value">{parseFloat(account.available_balance).toFixed(2)} USDT</span>
</div> </div>
<div className="info-item"> <div className="info-item">
<span className="label">总仓位:</span> <span className="label">总仓位(名义):</span>
<span className="value">{parseFloat(account.total_position_value).toFixed(2)} USDT</span> <span className="value">{parseFloat(account.total_position_value).toFixed(2)} USDT</span>
</div> </div>
{account.total_margin_value !== undefined && account.total_margin_value !== null && (
<div className="info-item">
<span className="label">保证金占用(估算):</span>
<span className="value">{parseFloat(account.total_margin_value).toFixed(2)} USDT</span>
</div>
)}
<div className="info-item"> <div className="info-item">
<span className="label">总盈亏:</span> <span className="label">总盈亏:</span>
<span className={`value ${parseFloat(account.total_pnl) >= 0 ? 'positive' : 'negative'}`}> <span className={`value ${parseFloat(account.total_pnl) >= 0 ? 'positive' : 'negative'}`}>
@ -189,7 +195,7 @@ const StatsDashboard = () => {
<h3>仓位占比</h3> <h3>仓位占比</h3>
<div className="account-info"> <div className="account-info">
<div className="info-item"> <div className="info-item">
<span className="label">当前仓位占比:</span> <span className="label">当前仓位占比(保证金):</span>
<span className="value"> <span className="value">
<span>{dashboardData.position_stats.current_position_percent}%</span> <span>{dashboardData.position_stats.current_position_percent}%</span>
<span className="position-bar-container"> <span className="position-bar-container">
@ -211,16 +217,22 @@ const StatsDashboard = () => {
<span className="label">最大仓位占比:</span> <span className="label">最大仓位占比:</span>
<span className="value">{dashboardData.position_stats.max_position_percent}%</span> <span className="value">{dashboardData.position_stats.max_position_percent}%</span>
</div> </div>
{dashboardData.position_stats.current_notional_percent !== undefined && (
<div className="info-item"> <div className="info-item">
<span className="label">最大仓位量:</span> <span className="label">名义占比(参考):</span>
<span className="value">{dashboardData.position_stats.current_notional_percent}%</span>
</div>
)}
<div className="info-item">
<span className="label">最大保证金:</span>
<span className="value">{dashboardData.position_stats.max_position_value.toFixed(2)} USDT</span> <span className="value">{dashboardData.position_stats.max_position_value.toFixed(2)} USDT</span>
</div> </div>
<div className="info-item"> <div className="info-item">
<span className="label">已用仓位:</span> <span className="label">已用保证金:</span>
<span className="value">{dashboardData.position_stats.total_position_value.toFixed(2)} USDT</span> <span className="value">{dashboardData.position_stats.total_position_value.toFixed(2)} USDT</span>
</div> </div>
<div className="info-item"> <div className="info-item">
<span className="label">可用仓位:</span> <span className="label">可用保证金:</span>
<span className="value"> <span className="value">
{(dashboardData.position_stats.max_position_value - dashboardData.position_stats.total_position_value).toFixed(2)} USDT {(dashboardData.position_stats.max_position_value - dashboardData.position_stats.total_position_value).toFixed(2)} USDT
</span> </span>