This commit is contained in:
薇薇安 2026-01-22 18:51:26 +08:00
parent 244ef6f4ba
commit 06272b6922

View File

@ -6,7 +6,7 @@ const TradeList = () => {
const [trades, setTrades] = useState([]) const [trades, setTrades] = useState([])
const [stats, setStats] = useState(null) const [stats, setStats] = useState(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
// //
const [period, setPeriod] = useState('today') // '1d', '7d', '30d', 'today', 'week', 'month', null const [period, setPeriod] = useState('today') // '1d', '7d', '30d', 'today', 'week', 'month', null
const [startDate, setStartDate] = useState('') const [startDate, setStartDate] = useState('')
@ -19,13 +19,13 @@ const TradeList = () => {
useEffect(() => { useEffect(() => {
loadData() loadData()
// //
const handleAccountChange = () => { const handleAccountChange = () => {
loadData() loadData()
} }
window.addEventListener('ats:account:changed', handleAccountChange) window.addEventListener('ats:account:changed', handleAccountChange)
return () => { return () => {
window.removeEventListener('ats:account:changed', handleAccountChange) window.removeEventListener('ats:account:changed', handleAccountChange)
} }
@ -37,7 +37,7 @@ const TradeList = () => {
const params = { const params = {
limit: 100 limit: 100
} }
// 使 // 使
if (!useCustomDate && period) { if (!useCustomDate && period) {
params.period = period params.period = period
@ -46,12 +46,12 @@ const TradeList = () => {
if (startDate) params.start_date = startDate if (startDate) params.start_date = startDate
if (endDate) params.end_date = endDate if (endDate) params.end_date = endDate
} }
if (symbol) params.symbol = symbol if (symbol) params.symbol = symbol
if (status) params.status = status if (status) params.status = status
if (tradeType) params.trade_type = tradeType if (tradeType) params.trade_type = tradeType
if (exitReason) params.exit_reason = exitReason if (exitReason) params.exit_reason = exitReason
const [tradesData, statsData] = await Promise.all([ const [tradesData, statsData] = await Promise.all([
api.getTrades(params), api.getTrades(params),
api.getTradeStats(params) api.getTradeStats(params)
@ -96,49 +96,49 @@ const TradeList = () => {
<p style={{ color: '#666', fontSize: '14px', marginTop: '-10px', marginBottom: '20px' }}> <p style={{ color: '#666', fontSize: '14px', marginTop: '-10px', marginBottom: '20px' }}>
说明每条记录代表一笔完整的交易开仓+平仓统计总盈亏时每条记录只计算一次 说明每条记录代表一笔完整的交易开仓+平仓统计总盈亏时每条记录只计算一次
</p> </p>
{/* 筛选面板 */} {/* 筛选面板 */}
<div className="filter-panel"> <div className="filter-panel">
<div className="filter-section"> <div className="filter-section">
<label>快速筛选</label> <label>快速筛选</label>
<div className="period-buttons"> <div className="period-buttons">
<button <button
className={period === 'today' ? 'active' : ''} className={period === 'today' ? 'active' : ''}
onClick={() => handlePeriodChange('today')} onClick={() => handlePeriodChange('today')}
> >
今天 今天
</button> </button>
<button <button
className={period === 'week' ? 'active' : ''} className={period === 'week' ? 'active' : ''}
onClick={() => handlePeriodChange('week')} onClick={() => handlePeriodChange('week')}
> >
本周 本周
</button> </button>
<button <button
className={period === 'month' ? 'active' : ''} className={period === 'month' ? 'active' : ''}
onClick={() => handlePeriodChange('month')} onClick={() => handlePeriodChange('month')}
> >
本月 本月
</button> </button>
<button <button
className={period === '1d' ? 'active' : ''} className={period === '1d' ? 'active' : ''}
onClick={() => handlePeriodChange('1d')} onClick={() => handlePeriodChange('1d')}
> >
最近1天 最近1天
</button> </button>
<button <button
className={period === '7d' ? 'active' : ''} className={period === '7d' ? 'active' : ''}
onClick={() => handlePeriodChange('7d')} onClick={() => handlePeriodChange('7d')}
> >
最近7天 最近7天
</button> </button>
<button <button
className={period === '30d' ? 'active' : ''} className={period === '30d' ? 'active' : ''}
onClick={() => handlePeriodChange('30d')} onClick={() => handlePeriodChange('30d')}
> >
最近30天 最近30天
</button> </button>
<button <button
className={period === null && !useCustomDate ? 'active' : ''} className={period === null && !useCustomDate ? 'active' : ''}
onClick={() => handlePeriodChange(null)} onClick={() => handlePeriodChange(null)}
> >
@ -149,8 +149,8 @@ const TradeList = () => {
<div className="filter-section"> <div className="filter-section">
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={useCustomDate} checked={useCustomDate}
onChange={handleCustomDateToggle} onChange={handleCustomDateToggle}
/> />
@ -248,14 +248,34 @@ const TradeList = () => {
<div className="stat-value">总盈亏{stats.total_pnl.toFixed(2)} USDT</div> <div className="stat-value">总盈亏{stats.total_pnl.toFixed(2)} USDT</div>
<div className="stat-value">平均盈亏{stats.avg_pnl.toFixed(2)} USDT</div> <div className="stat-value">平均盈亏{stats.avg_pnl.toFixed(2)} USDT</div>
<div className="stat-value">平均持仓时长分钟{Number(stats.avg_duration_minutes || 0).toFixed(0)}</div> <div className="stat-value">平均持仓时长分钟{Number(stats.avg_duration_minutes || 0).toFixed(0)}</div>
<div className="stat-value">平仓原因有意义交易{stats.exit_reason_counts.join(' / ')}</div> <div className="stat-value">平仓原因有意义交易
<div className="stat-value" style={{ fontSize: '1.1rem' }}>
{(() => {
const m = stats.exit_reason_counts || {}
const stopLoss = Number(m.stop_loss || 0)
const takeProfit = Number(m.take_profit || 0)
const trailing = Number(m.trailing_stop || 0)
const manual = Number(m.manual || 0)
const sync = Number(m.sync || 0)
const other = Number(m.unknown || 0)
const parts = []
if (stopLoss) parts.push(`止损 ${stopLoss}`)
if (takeProfit) parts.push(`止盈 ${takeProfit}`)
if (trailing) parts.push(`移动止损 ${trailing}`)
if (manual) parts.push(`手动 ${manual}`)
if (sync) parts.push(`同步 ${sync}`)
if (other) parts.push(`其他 ${other}`)
return parts.length ? parts.join(' / ') : '—'
})()}
</div>
</div>
<div className="stat-value">平均盈利 / 平均亏损期望 3:1{stats.avg_win_loss_ratio.toFixed(2)} : 1</div> <div className="stat-value">平均盈利 / 平均亏损期望 3:1{stats.avg_win_loss_ratio.toFixed(2)} : 1</div>
<div className="stat-value">总交易量名义{stats.total_notional_usdt.toFixed(2)} USDT</div> <div className="stat-value">总交易量名义{stats.total_notional_usdt.toFixed(2)} USDT</div>
</div> </div>
</div> </div>
) )
} }
{stats && ( {stats && (
<div className="stats-summary"> <div className="stats-summary">
<div className="stat-card"> <div className="stat-card">
@ -324,9 +344,8 @@ const TradeList = () => {
<div className="stat-card"> <div className="stat-card">
<div className="stat-label">平均盈利 / 平均亏损期望 3:1</div> <div className="stat-label">平均盈利 / 平均亏损期望 3:1</div>
<div <div
className={`stat-value ${ className={`stat-value ${typeof stats.avg_win_loss_ratio === 'number' && stats.avg_win_loss_ratio >= 3 ? 'positive' : ''
typeof stats.avg_win_loss_ratio === 'number' && stats.avg_win_loss_ratio >= 3 ? 'positive' : '' }`}
}`}
> >
{typeof stats.avg_win_loss_ratio === 'number' {typeof stats.avg_win_loss_ratio === 'number'
? `${stats.avg_win_loss_ratio.toFixed(2)} : 1` ? `${stats.avg_win_loss_ratio.toFixed(2)} : 1`
@ -380,19 +399,19 @@ const TradeList = () => {
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
? parseFloat(trade.notional_usdt) ? parseFloat(trade.notional_usdt)
: ( : (
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
? parseFloat(trade.entry_value_usdt) ? parseFloat(trade.entry_value_usdt)
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0)) : (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
) )
const leverage = parseFloat(trade.leverage || 10) const leverage = parseFloat(trade.leverage || 10)
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
? parseFloat(trade.margin_usdt) ? parseFloat(trade.margin_usdt)
: (leverage > 0 ? notional / leverage : 0) : (leverage > 0 ? notional / leverage : 0)
// / // /
const pnl = parseFloat(trade.pnl || 0) const pnl = parseFloat(trade.pnl || 0)
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0 const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
// //
// Unix // Unix
const formatTime = (timeValue) => { const formatTime = (timeValue) => {
@ -418,7 +437,7 @@ const TradeList = () => {
return String(timeValue) return String(timeValue)
} }
} }
// //
const formatOrderIds = () => { const formatOrderIds = () => {
const entry = trade.entry_order_id || '-' const entry = trade.entry_order_id || '-'
@ -429,7 +448,7 @@ const TradeList = () => {
} }
return entry !== '-' ? `开仓: ${entry}` : `平仓: ${exit}` return entry !== '-' ? `开仓: ${entry}` : `平仓: ${exit}`
} }
return ( return (
<tr key={trade.id}> <tr key={trade.id}>
<td style={{ fontSize: '12px', color: '#999' }}>#{trade.id}</td> <td style={{ fontSize: '12px', color: '#999' }}>#{trade.id}</td>
@ -458,7 +477,7 @@ const TradeList = () => {
})} })}
</tbody> </tbody>
</table> </table>
{/* 移动端卡片 */} {/* 移动端卡片 */}
<div className="trades-cards"> <div className="trades-cards">
{trades.map(trade => { {trades.map(trade => {
@ -466,17 +485,17 @@ const TradeList = () => {
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
? parseFloat(trade.notional_usdt) ? parseFloat(trade.notional_usdt)
: ( : (
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
? parseFloat(trade.entry_value_usdt) ? parseFloat(trade.entry_value_usdt)
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0)) : (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
) )
const leverage = parseFloat(trade.leverage || 10) const leverage = parseFloat(trade.leverage || 10)
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
? parseFloat(trade.margin_usdt) ? parseFloat(trade.margin_usdt)
: (leverage > 0 ? notional / leverage : 0) : (leverage > 0 ? notional / leverage : 0)
const pnl = parseFloat(trade.pnl || 0) const pnl = parseFloat(trade.pnl || 0)
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0 const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
// //
// Unix // Unix
const formatTime = (timeValue) => { const formatTime = (timeValue) => {
@ -501,7 +520,7 @@ const TradeList = () => {
return String(timeValue) return String(timeValue)
} }
} }
return ( return (
<div key={trade.id} className="trade-card"> <div key={trade.id} className="trade-card">
<div className="trade-card-header"> <div className="trade-card-header">
@ -562,17 +581,17 @@ const TradeList = () => {
)} )}
</div> </div>
<div className="trade-card-footer"> <div className="trade-card-footer">
<div className="trade-time-item">
<span className="time-label">入场:</span>
<span>{formatTime(trade.entry_time)}</span>
</div>
{trade.exit_time && (
<div className="trade-time-item"> <div className="trade-time-item">
<span className="time-label">平仓:</span> <span className="time-label">入场:</span>
<span>{formatTime(trade.exit_time)}</span> <span>{formatTime(trade.entry_time)}</span>
</div> </div>
)} {trade.exit_time && (
</div> <div className="trade-time-item">
<span className="time-label">平仓:</span>
<span>{formatTime(trade.exit_time)}</span>
</div>
)}
</div>
</div> </div>
) )
})} })}