diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index 17b91c6..285337d 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -135,7 +135,10 @@ async def get_realtime_account_data(): # 计算总仓位价值和总盈亏 logger.info("步骤7: 计算仓位统计...") + # total_position_value:历史上这里代表“名义仓位价值(notional)”(按标记价) total_position_value = 0 + # total_margin_value:更贴近风控配置语义(保证金占用) + total_margin_value = 0 total_pnl = 0 open_positions_count = 0 @@ -154,13 +157,23 @@ async def get_realtime_account_data(): position_value = abs(position_amt * mark_price) 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 open_positions_count += 1 logger.debug(f" - {pos.get('symbol')}: 价值={position_value:.2f}, 盈亏={unrealized_pnl:.2f}") 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" - 持仓数量: {open_positions_count}") @@ -176,7 +189,10 @@ async def get_realtime_account_data(): result = { "total_balance": balance.get('total', 0) if balance else 0, "available_balance": balance.get('available', 0) if balance else 0, + # 名义仓位(按标记价汇总) "total_position_value": total_position_value, + # 保证金占用(名义/杠杆汇总) + "total_margin_value": total_margin_value, "total_pnl": total_pnl, "open_positions": open_positions_count } diff --git a/backend/api/routes/stats.py b/backend/api/routes/stats.py index bf96713..25d61bf 100644 --- a/backend/api/routes/stats.py +++ b/backend/api/routes/stats.py @@ -176,21 +176,57 @@ async def get_dashboard_data(): try: from database.models import TradingConfig 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)) - - # 当前仓位占比 - current_position_percent = (total_position_value / total_balance * 100) if total_balance > 0 else 0 - - # 最大仓位量(根据配置的最大占比计算) - max_position_value = total_balance * max_total_position_percent + + # 名义仓位(notional)与保证金占用(margin)是两个口径: + # - 名义仓位可以 > 100%(高杠杆下非常正常) + # - MAX_TOTAL_POSITION_PERCENT 在当前系统语义里是“保证金占用比例” + total_notional_value = float(account_data.get('total_position_value', 0)) + + # 优先使用 account_data 里的 total_margin_value;如果没有则从 open_trades 汇总兜底 + 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 = { - "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_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_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: logger.warning(f"计算仓位占比信息失败: {e}") diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index 9d2f22c..4ac138e 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -165,9 +165,15 @@ const StatsDashboard = () => { {parseFloat(account.available_balance).toFixed(2)} USDT