fix: db migration tab char corrupted, restore historical trades
This commit is contained in:
+78
-28
@@ -198,6 +198,13 @@ function StatsCard({ stats }) {
|
||||
connHtml = Object.entries(stats.connections)
|
||||
.map(([ex, status]) => `${ex}:${status}`).join(' ')
|
||||
}
|
||||
// Format exchange funds
|
||||
let exFundsHtml = ''
|
||||
if (stats.exchange_funds) {
|
||||
exFundsHtml = Object.entries(stats.exchange_funds)
|
||||
.map(([ex, f]) => `${ex}: $${f.balance.toFixed(2)}`)
|
||||
.join(' | ')
|
||||
}
|
||||
return (
|
||||
<section className="card" id="stats-card">
|
||||
<h2>📊 统计数据</h2>
|
||||
@@ -210,6 +217,11 @@ function StatsCard({ stats }) {
|
||||
<div className="stat"><label>币种</label><span className="pct-blue">{stats.coins || 0}</span></div>
|
||||
<div className="stat" id="conn-stats"><label>连接</label><span id="conn-detail" style={{fontSize:11}}>{connHtml}</span></div>
|
||||
</div>
|
||||
{exFundsHtml && (
|
||||
<div className="stats-row" style={{ marginTop: 2, fontSize: 11, opacity: 0.85 }}>
|
||||
<div className="stat" style={{gridColumn:'1 / -1'}}><label>资金</label><span style={{fontWeight:600}}>{exFundsHtml}</span></div>
|
||||
</div>
|
||||
)}
|
||||
{d && (
|
||||
<div className="stats-row detail-stats" style={{ marginTop: 4, fontSize: 12, opacity: 0.85 }}>
|
||||
<div className="stat"><label>总PnL</label><span>{(d.total_pnl_usd != null ? '$' + d.total_pnl_usd.toFixed(2) : '—') + (d.capital_pnl != null ? ' (' + d.capital_pnl.toFixed(4) + '%)' : '')}</span></div>
|
||||
@@ -225,35 +237,73 @@ function StatsCard({ stats }) {
|
||||
}
|
||||
|
||||
function PositionsCard({ positions }) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [modalTrade, setModalTrade] = useState(null)
|
||||
const [modalOrders, setModalOrders] = useState([])
|
||||
|
||||
function openPositionDetail(id) {
|
||||
if (!id) return
|
||||
setModalOpen(true)
|
||||
setModalTrade(null)
|
||||
setModalOrders([])
|
||||
fetch('/api/trade/' + id)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
setModalTrade(data.trade)
|
||||
setModalOrders(data.orders || [])
|
||||
})
|
||||
.catch(() => {
|
||||
setModalTrade({ ID: id })
|
||||
})
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
setModalOpen(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!modalOpen) return
|
||||
function handler(e) {
|
||||
if (e.key === 'Escape') closeModal()
|
||||
}
|
||||
document.addEventListener('keydown', handler)
|
||||
return () => document.removeEventListener('keydown', handler)
|
||||
}, [modalOpen])
|
||||
|
||||
return (
|
||||
<section className="card" id="positions-card">
|
||||
<h2>🔒 当前持仓</h2>
|
||||
<div className="table-wrap">
|
||||
<table id="positions-table">
|
||||
<thead>
|
||||
<tr><th>币种</th><th>方向</th><th>规模</th><th>入价差</th><th>现价差</th><th>估盈亏</th><th>加仓</th><th>时长</th></tr>
|
||||
</thead>
|
||||
<tbody id="positions-body">
|
||||
{positions.length === 0 ? (
|
||||
<tr><td colSpan="8" className="loading">无持仓</td></tr>
|
||||
) : (
|
||||
[...positions].sort((a, b) => a.coin.localeCompare(b.coin)).map(p => (
|
||||
<tr key={p.coin}>
|
||||
<td><strong>{p.coin}</strong></td>
|
||||
<td>{p.direction}</td>
|
||||
<td className="text-right">${(p.amount_usd || 0).toFixed(0)}</td>
|
||||
<td className="text-right">{(p.entry_spread || 0).toFixed(4)}%</td>
|
||||
<td className="text-right">{p.current_spread != null ? p.current_spread.toFixed(4) + '%' : '-'}</td>
|
||||
<td className={'text-right ' + pnlClass(p.pnl_est)}><strong>{p.pnl_est != null ? '$' + p.pnl_est.toFixed(4) : '-'}</strong></td>
|
||||
<td className="text-right">{p.scales || 0}</td>
|
||||
<td>{p.duration || '-'}</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<>
|
||||
<section className="card" id="positions-card">
|
||||
<h2>🔒 当前持仓</h2>
|
||||
<div className="table-wrap">
|
||||
<table id="positions-table">
|
||||
<thead>
|
||||
<tr><th>币种</th><th>方向</th><th>规模</th><th>入价差</th><th>现价差</th><th>估盈亏</th><th>加仓</th><th>时长</th></tr>
|
||||
</thead>
|
||||
<tbody id="positions-body">
|
||||
{positions.length === 0 ? (
|
||||
<tr><td colSpan="8" className="loading">无持仓</td></tr>
|
||||
) : (
|
||||
[...positions].sort((a, b) => a.coin.localeCompare(b.coin)).map(p => (
|
||||
<tr key={p.coin} className="trade-row" onClick={() => openPositionDetail(p.db_trade_id)}>
|
||||
<td><strong>{p.coin}</strong></td>
|
||||
<td>{p.direction}</td>
|
||||
<td className="text-right">${(p.amount_usd || 0).toFixed(0)}</td>
|
||||
<td className="text-right">{(p.entry_spread || 0).toFixed(4)}%</td>
|
||||
<td className="text-right">{p.current_spread != null ? p.current_spread.toFixed(4) + '%' : '-'}</td>
|
||||
<td className={'text-right ' + pnlClass(p.pnl_est)}><strong>{p.pnl_est != null ? '$' + p.pnl_est.toFixed(4) : '-'}</strong></td>
|
||||
<td className="text-right">{p.scales || 0}</td>
|
||||
<td>{p.duration || '-'}</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{modalOpen && (
|
||||
<TradeDetailModal trade={modalTrade} orders={modalOrders} onClose={closeModal} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user