diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index 8ab3ade..ce1f763 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -3,6 +3,7 @@ """ from fastapi import APIRouter, HTTPException, Header, Depends from fastapi import Query +import asyncio import sys from pathlib import Path import logging @@ -640,6 +641,39 @@ async def get_realtime_positions(account_id: int = Depends(get_account_id)): logger.info(f"获取到 {len(positions)} 个持仓") + # 并发获取所有持仓的挂单信息 + open_orders_map = {} + try: + position_symbols = [p.get('symbol') for p in positions if float(p.get('positionAmt', 0)) != 0] + if position_symbols: + logger.info(f"正在获取挂单信息: {position_symbols}") + tasks = [client.get_open_orders(sym) for sym in position_symbols] + results = await asyncio.gather(*tasks, return_exceptions=True) + + for sym, orders in zip(position_symbols, results): + if isinstance(orders, list): + # 过滤出止盈止损单 + conditional_orders = [] + for o in orders: + o_type = o.get('type') + if o_type in ['STOP_MARKET', 'TAKE_PROFIT_MARKET', 'STOP', 'TAKE_PROFIT']: + conditional_orders.append({ + 'orderId': o.get('orderId'), + 'type': o_type, + 'side': o.get('side'), + 'stopPrice': float(o.get('stopPrice', 0)), + 'price': float(o.get('price', 0)), + 'origType': o.get('origType'), + 'reduceOnly': o.get('reduceOnly'), + 'status': o.get('status') + }) + if conditional_orders: + open_orders_map[sym] = conditional_orders + else: + logger.warning(f"获取 {sym} 挂单失败: {orders}") + except Exception as e: + logger.error(f"批量获取挂单失败: {e}") + # 格式化持仓数据 formatted_positions = [] for pos in positions: @@ -754,6 +788,7 @@ async def get_realtime_positions(account_id: int = Depends(get_account_id)): "atr": atr_value, "entry_order_id": entry_order_id, "entry_order_type": entry_order_type, # LIMIT / MARKET(用于仪表板展示“限价/市价”) + "open_orders": open_orders_map.get(pos.get('symbol'), []), # 实时挂单信息 }) logger.info(f"格式化后 {len(formatted_positions)} 个有效持仓") diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index 5cb4755..76c62ca 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -282,7 +282,23 @@ const StatsDashboard = () => { const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-') const filename = `持仓记录_${timestamp}.json` - const dataStr = JSON.stringify(openTrades, null, 2) + + // 增强导出数据:添加易读的挂单汇总信息 + const exportData = openTrades.map(trade => { + const slOrders = trade.open_orders?.filter(o => o.type.includes('STOP')) || [] + const tpOrders = trade.open_orders?.filter(o => o.type.includes('TAKE_PROFIT')) || [] + + return { + ...trade, + // 添加易读的汇总字段 + active_sl_orders: slOrders.map(o => `${o.type} @ ${o.stopPrice} (${o.status})`).join('; '), + active_tp_orders: tpOrders.map(o => `${o.type} @ ${o.stopPrice || o.price} (${o.status})`).join('; '), + // 确保原始数据也在 + binance_open_orders_raw: trade.open_orders || [] + } + }) + + const dataStr = JSON.stringify(exportData, null, 2) const dataBlob = new Blob([dataStr], { type: 'application/json' }) const url = URL.createObjectURL(dataBlob) const link = document.createElement('a') @@ -736,6 +752,25 @@ const StatsDashboard = () => { )} + + {/* 显示币安实际挂单信息 */} + {trade.open_orders && trade.open_orders.length > 0 && ( +