a
This commit is contained in:
parent
244ef6f4ba
commit
06272b6922
|
|
@ -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>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user