diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index 7382997..a065500 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -267,15 +267,34 @@ async def get_realtime_positions(): if entry_price > 0 and position_value > 0: pnl_percent = (unrealized_pnl / position_value) * 100 + # 计算开仓时的USDT数量 + entry_value_usdt = abs(position_amt) * entry_price + + # 尝试从数据库获取开仓时间 + entry_time = None + try: + from database.models import Trade + db_trades = Trade.get_by_symbol(pos.get('symbol'), status='open') + if db_trades: + # 找到匹配的交易记录(通过symbol和entry_price匹配) + for db_trade in db_trades: + if abs(float(db_trade.get('entry_price', 0)) - entry_price) < 0.01: + entry_time = db_trade.get('entry_time') + break + except Exception as e: + logger.debug(f"获取开仓时间失败: {e}") + formatted_positions.append({ "symbol": pos.get('symbol'), "side": "BUY" if position_amt > 0 else "SELL", "quantity": abs(position_amt), "entry_price": entry_price, + "entry_value_usdt": entry_value_usdt, # 开仓时的USDT数量 "mark_price": mark_price, "pnl": unrealized_pnl, "pnl_percent": pnl_percent, - "leverage": int(pos.get('leverage', 1)) + "leverage": int(pos.get('leverage', 1)), + "entry_time": entry_time # 开仓时间 }) logger.info(f"格式化后 {len(formatted_positions)} 个有效持仓") diff --git a/backend/api/routes/stats.py b/backend/api/routes/stats.py index 8919f05..682952f 100644 --- a/backend/api/routes/stats.py +++ b/backend/api/routes/stats.py @@ -102,7 +102,19 @@ async def get_dashboard_data(): logger.warning(f"获取实时持仓失败 (HTTP {e.status_code}): {positions_error}") # 回退到数据库记录 try: - open_trades = Trade.get_all(status='open')[:10] + db_trades = Trade.get_all(status='open')[:10] + # 格式化数据库记录,添加 entry_value_usdt 字段 + open_trades = [] + for trade in db_trades: + entry_value_usdt = float(trade.get('quantity', 0)) * float(trade.get('entry_price', 0)) + formatted_trade = { + **trade, + 'entry_value_usdt': entry_value_usdt, + 'mark_price': trade.get('entry_price', 0), # 数据库中没有标记价,使用入场价 + 'pnl': trade.get('pnl', 0), + 'pnl_percent': trade.get('pnl_percent', 0) + } + open_trades.append(formatted_trade) logger.info(f"使用数据库记录作为持仓数据: {len(open_trades)} 个持仓") except Exception as db_error: logger.error(f"从数据库获取持仓记录失败: {db_error}") @@ -111,7 +123,19 @@ async def get_dashboard_data(): logger.warning(f"获取实时持仓失败: {positions_error}", exc_info=True) # 回退到数据库记录 try: - open_trades = Trade.get_all(status='open')[:10] + db_trades = Trade.get_all(status='open')[:10] + # 格式化数据库记录,添加 entry_value_usdt 字段 + open_trades = [] + for trade in db_trades: + entry_value_usdt = float(trade.get('quantity', 0)) * float(trade.get('entry_price', 0)) + formatted_trade = { + **trade, + 'entry_value_usdt': entry_value_usdt, + 'mark_price': trade.get('entry_price', 0), # 数据库中没有标记价,使用入场价 + 'pnl': trade.get('pnl', 0), + 'pnl_percent': trade.get('pnl_percent', 0) + } + open_trades.append(formatted_trade) logger.info(f"使用数据库记录作为持仓数据: {len(open_trades)} 个持仓") except Exception as db_error: logger.error(f"从数据库获取持仓记录失败: {db_error}") diff --git a/frontend/src/components/StatsDashboard.css b/frontend/src/components/StatsDashboard.css index 9552585..c3495b2 100644 --- a/frontend/src/components/StatsDashboard.css +++ b/frontend/src/components/StatsDashboard.css @@ -193,6 +193,12 @@ border-radius: 6px; } +.entry-time { + color: #999; + font-size: 0.8rem; + font-style: italic; +} + @media (min-width: 768px) { .trade-pnl { background: transparent; diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index 52b29b1..4a9322b 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -70,30 +70,69 @@ const StatsDashboard = () => {

当前持仓

{openTrades.length > 0 ? (
- {openTrades.map((trade, index) => ( -
-
{trade.symbol}
-
- {trade.side} + {openTrades.map((trade, index) => { + // 计算开仓USDT数量(如果后端没有提供,则计算) + const entryValueUsdt = trade.entry_value_usdt !== undefined + ? trade.entry_value_usdt + : (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0)) + + // 格式化开仓时间 + const formatEntryTime = (timeStr) => { + if (!timeStr) return null + try { + const date = new Date(timeStr) + const now = new Date() + const diffMs = now - date + const diffMins = Math.floor(diffMs / 60000) + const diffHours = Math.floor(diffMs / 3600000) + const diffDays = Math.floor(diffMs / 86400000) + + if (diffMins < 1) return '刚刚' + if (diffMins < 60) return `${diffMins}分钟前` + if (diffHours < 24) return `${diffHours}小时前` + if (diffDays < 7) return `${diffDays}天前` + + // 超过7天显示具体日期 + return date.toLocaleString('zh-CN', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }) + } catch (e) { + return timeStr + } + } + + return ( +
+
{trade.symbol}
+
+ {trade.side} +
+
+
开仓金额: {entryValueUsdt.toFixed(2)} USDT
+
数量: {parseFloat(trade.quantity || 0).toFixed(4)}
+
入场价: {parseFloat(trade.entry_price || 0).toFixed(4)}
+ {trade.mark_price && ( +
标记价: {parseFloat(trade.mark_price).toFixed(4)}
+ )} + {trade.leverage && ( +
杠杆: {trade.leverage}x
+ )} + {trade.entry_time && ( +
开仓时间: {formatEntryTime(trade.entry_time)}
+ )} +
+
= 0 ? 'positive' : 'negative'}`}> + {parseFloat(trade.pnl || 0).toFixed(2)} USDT + {trade.pnl_percent !== undefined && ( + ({parseFloat(trade.pnl_percent).toFixed(2)}%) + )} +
-
-
数量: {parseFloat(trade.quantity || 0).toFixed(4)}
-
入场价: {parseFloat(trade.entry_price || 0).toFixed(4)}
- {trade.mark_price && ( -
标记价: {parseFloat(trade.mark_price).toFixed(4)}
- )} - {trade.leverage && ( -
杠杆: {trade.leverage}x
- )} -
-
= 0 ? 'positive' : 'negative'}`}> - {parseFloat(trade.pnl || 0).toFixed(2)} USDT - {trade.pnl_percent !== undefined && ( - ({parseFloat(trade.pnl_percent).toFixed(2)}%) - )} -
-
- ))} + ) + })}
) : (
暂无持仓