11
This commit is contained in:
parent
bfae183e39
commit
cc324eead5
|
|
@ -3,6 +3,7 @@
|
||||||
"""
|
"""
|
||||||
from fastapi import APIRouter, HTTPException, Header, Depends
|
from fastapi import APIRouter, HTTPException, Header, Depends
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -640,6 +641,39 @@ async def get_realtime_positions(account_id: int = Depends(get_account_id)):
|
||||||
|
|
||||||
logger.info(f"获取到 {len(positions)} 个持仓")
|
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 = []
|
formatted_positions = []
|
||||||
for pos in positions:
|
for pos in positions:
|
||||||
|
|
@ -754,6 +788,7 @@ async def get_realtime_positions(account_id: int = Depends(get_account_id)):
|
||||||
"atr": atr_value,
|
"atr": atr_value,
|
||||||
"entry_order_id": entry_order_id,
|
"entry_order_id": entry_order_id,
|
||||||
"entry_order_type": entry_order_type, # LIMIT / MARKET(用于仪表板展示“限价/市价”)
|
"entry_order_type": entry_order_type, # LIMIT / MARKET(用于仪表板展示“限价/市价”)
|
||||||
|
"open_orders": open_orders_map.get(pos.get('symbol'), []), # 实时挂单信息
|
||||||
})
|
})
|
||||||
|
|
||||||
logger.info(f"格式化后 {len(formatted_positions)} 个有效持仓")
|
logger.info(f"格式化后 {len(formatted_positions)} 个有效持仓")
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,23 @@ const StatsDashboard = () => {
|
||||||
|
|
||||||
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')
|
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')
|
||||||
const filename = `持仓记录_${timestamp}.json`
|
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 dataBlob = new Blob([dataStr], { type: 'application/json' })
|
||||||
const url = URL.createObjectURL(dataBlob)
|
const url = URL.createObjectURL(dataBlob)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
|
|
@ -736,6 +752,25 @@ const StatsDashboard = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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-actions">
|
||||||
<div className={`trade-pnl ${parseFloat(trade.pnl || 0) >= 0 ? 'positive' : 'negative'}`}>
|
<div className={`trade-pnl ${parseFloat(trade.pnl || 0) >= 0 ? 'positive' : 'negative'}`}>
|
||||||
{parseFloat(trade.pnl || 0).toFixed(2)} USDT
|
{parseFloat(trade.pnl || 0).toFixed(2)} USDT
|
||||||
|
|
|
||||||
|
|
@ -2635,6 +2635,44 @@ class PositionManager:
|
||||||
import traceback
|
import traceback
|
||||||
logger.error(f" 错误详情:\n{traceback.format_exc()}")
|
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("持仓状态同步完成")
|
logger.info("持仓状态同步完成")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user