This commit is contained in:
薇薇安 2026-02-08 20:21:21 +08:00
parent bfae183e39
commit cc324eead5
3 changed files with 109 additions and 1 deletions

View File

@ -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)} 个有效持仓")

View File

@ -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 = () => {
</div>
)}
</div>
{/* 显示币安实际挂单信息 */}
{trade.open_orders && trade.open_orders.length > 0 && (
<div className="open-orders-info" style={{ marginTop: '8px', borderTop: '1px dashed #eee', paddingTop: '6px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', fontWeight: 'bold' }}>条件单 (Binance):</div>
{trade.open_orders.map(order => (
<div key={order.orderId} style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px', marginBottom: '4px', paddingLeft: '8px' }}>
<span className={order.side === 'BUY' ? 'positive' : 'negative'} style={{ fontWeight: '500' }}>
{order.type.replace('_MARKET', '')}
</span>
<span style={{ fontFamily: 'monospace' }}>
{order.type.includes('STOP') ? `触发价: ${fmtPrice(order.stopPrice)}` : `价格: ${fmtPrice(order.price)}`}
</span>
<span style={{ color: '#999', fontSize: '11px' }}>{order.status}</span>
</div>
))}
</div>
)}
<div className="trade-actions">
<div className={`trade-pnl ${parseFloat(trade.pnl || 0) >= 0 ? 'positive' : 'negative'}`}>
{parseFloat(trade.pnl || 0).toFixed(2)} USDT

View File

@ -2635,6 +2635,44 @@ class PositionManager:
import traceback
logger.error(f" 错误详情:\n{traceback.format_exc()}")
# 6. 同步挂单信息 (STOP_MARKET / TAKE_PROFIT_MARKET)
if self.active_positions:
logger.debug("正在同步持仓挂单信息...")
try:
tasks = []
symbols = list(self.active_positions.keys())
for symbol in symbols:
tasks.append(self.client.get_open_orders(symbol))
results = await asyncio.gather(*tasks, return_exceptions=True)
for symbol, orders in zip(symbols, results):
if isinstance(orders, list):
# Filter for relevant orders (SL/TP)
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 symbol in self.active_positions:
self.active_positions[symbol]['openOrders'] = conditional_orders
logger.debug(f"{symbol} 同步挂单: {len(conditional_orders)}")
else:
logger.warning(f"{symbol} 获取挂单失败: {orders}")
except Exception as e:
logger.error(f"同步挂单信息失败: {e}")
logger.info("持仓状态同步完成")
except Exception as e: