This commit is contained in:
薇薇安 2026-03-01 14:18:36 +08:00
parent 7bc384a58f
commit 1e55365d43

View File

@ -12,7 +12,7 @@ const TradeList = () => {
const [showStatsTable, setShowStatsTable] = useState(false) //
//
const [period, setPeriod] = useState('today') // '1d', '7d', '30d', 'today', 'week', 'month', null
const [period, setPeriod] = useState('7d') // 7 3
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
const [symbol, setSymbol] = useState('')
@ -20,17 +20,12 @@ const TradeList = () => {
const [useCustomDate, setUseCustomDate] = useState(false)
const [tradeType, setTradeType] = useState('')
const [exitReason, setExitReason] = useState('')
const [reconciledOnly, setReconciledOnly] = useState(true) //
const [timeFilter, setTimeFilter] = useState('exit') // 'exit' =, 'entry'
const [syncing, setSyncing] = useState(false) //
const [syncResult, setSyncResult] = useState(null) //
const [syncDays, setSyncDays] = useState(7) //
const [syncAllSymbols, setSyncAllSymbols] = useState(false) //
const [dataSource, setDataSource] = useState('binance') // 'binance' | 'local'
const [timeFilter, setTimeFilter] = useState('exit')
const [dataSource, setDataSource] = useState('binance') // 'binance' | 'local'
useEffect(() => {
loadData()
}, [accountId, dataSource, reconciledOnly, timeFilter, period, useCustomDate, startDate, endDate, symbol, status, tradeType, exitReason])
}, [accountId, dataSource, timeFilter, period, useCustomDate, startDate, endDate, symbol, status, tradeType, exitReason])
const loadData = async () => {
setLoading(true)
@ -55,7 +50,7 @@ const TradeList = () => {
if (status) params.status = status
if (tradeType) params.trade_type = tradeType
if (exitReason) params.exit_reason = exitReason
params.reconciled_only = reconciledOnly
params.reconciled_only = false
params.time_filter = timeFilter || 'exit'
}
@ -101,58 +96,15 @@ const TradeList = () => {
}
const handleReset = () => {
setPeriod(null)
setPeriod('7d')
setStartDate('')
setEndDate('')
setSymbol('')
setStatus('')
setUseCustomDate(false)
setReconciledOnly(true)
}
//
const handleSyncOrders = async () => {
if (syncing) {
return //
}
const confirmMsg = syncAllSymbols
? `确定要同步最近 ${syncDays} 天的所有交易对订单吗?\n这将从币安拉取所有交易对的历史订单并创建缺失的交易记录。\n⚠ 注意:这会请求大量数据,可能需要较长时间。`
: `确定要同步最近 ${syncDays} 天的订单吗?\n这将从币安拉取历史订单并补全缺失的订单号仅限数据库中已有的交易对`
if (!window.confirm(confirmMsg)) {
return
}
setSyncing(true)
setSyncResult(null)
try {
const result = await api.syncTradesFromBinance(syncDays, syncAllSymbols)
const backfillHint = (result.entry_order_id_filled || result.exit_order_id_filled)
? `,补全开仓订单号 ${result.entry_order_id_filled || 0} 个、平仓订单号 ${result.exit_order_id_filled || 0} 个(勾选「仅可对账」后可见)`
: ''
setSyncResult({
success: true,
message: `同步完成:共处理 ${result.total_orders || 0} 个订单,更新 ${result.updated_trades || 0} 条记录${result.created_trades ? `,创建 ${result.created_trades} 条新记录` : ''}${backfillHint}`,
details: result
})
//
setTimeout(() => {
loadData()
}, 1000)
} catch (error) {
setSyncResult({
success: false,
message: `同步失败:${error.message || '未知错误'}`,
details: null
})
} finally {
setSyncing(false)
}
}
// /便
//
// type: 'csv' | 'json'
const handleExport = (type = 'csv') => {
if (trades.length === 0) {
@ -390,69 +342,13 @@ const TradeList = () => {
return (
<div className="trade-list">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '10px' }}>
<div>
<h2 style={{ margin: 0 }}>交易记录</h2>
<p style={{ color: '#666', fontSize: '14px', marginTop: '5px', marginBottom: '0' }}>
{dataSource === 'binance'
? '数据源币安成交binance_trades 表,与交易所一致)。由定时任务每 3 小时的第 0 分钟同步,存在一定延时(最多约 3 小时);需先在服务器运行 scripts/sync_binance_orders.py。'
: '说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。时间依据:按平仓时间=今日平仓+今日开仓未平仓;按开仓时间=实际入场时间,适合策略分析;按创建时间=记录写入 DB 时间。'}
</p>
</div>
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '8px',
alignItems: 'flex-end',
padding: '10px',
backgroundColor: '#f5f5f5',
borderRadius: '4px',
border: '1px solid #ddd'
}}>
<div style={{ fontSize: '12px', fontWeight: 'bold', color: '#666', marginBottom: '4px' }}>订单同步</div>
<p style={{ fontSize: '11px', color: '#888', margin: '0 0 6px 0' }}>
同步的是当前选中账号的币安订单若系统里几乎没有记录请勾选全量同步并选 7 天或 30
</p>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
<select
value={syncDays}
onChange={(e) => setSyncDays(Number(e.target.value))}
disabled={syncing}
style={{ padding: '4px 8px', fontSize: '12px', width: '70px' }}
>
<option value={3}>3</option>
<option value={7}>7</option>
<option value={14}>14</option>
<option value={30}>30</option>
</select>
<label style={{ display: 'flex', alignItems: 'center', gap: '4px', fontSize: '12px', cursor: 'pointer' }}>
<input
type="checkbox"
checked={syncAllSymbols}
onChange={(e) => setSyncAllSymbols(e.target.checked)}
disabled={syncing}
style={{ cursor: 'pointer' }}
/>
<span title="勾选后将同步所有交易对的订单(不限于数据库中的),用于补全缺失的交易记录">全量同步</span>
</label>
<button
onClick={handleSyncOrders}
disabled={syncing}
style={{
backgroundColor: syncing ? '#ccc' : '#4CAF50',
color: 'white',
border: 'none',
padding: '6px 12px',
fontSize: '12px',
borderRadius: '4px',
cursor: syncing ? 'not-allowed' : 'pointer'
}}
title={syncAllSymbols ? "从币安同步所有交易对的历史订单,并创建缺失的交易记录(会请求大量数据)" : "从币安同步历史订单,补全缺失的订单号(仅限数据库中已有的交易对,不会重复同步)"}
>
{syncing ? '同步中...' : '同步订单'}
</button>
</div>
</div>
<div style={{ marginBottom: '10px' }}>
<h2 style={{ margin: 0 }}>交易记录</h2>
<p style={{ color: '#666', fontSize: '14px', marginTop: '5px', marginBottom: '0' }}>
{dataSource === 'binance'
? '数据源币安成交binance_trades 表,与交易所一致)。由定时任务每 3 小时同步;默认显示最近 7 天。'
: '数据源:本地 trades 表(开仓/平仓回合)。时间依据:按平仓时间 / 开仓时间 / 创建时间。'}
</p>
</div>
{/* 筛选面板 */}
@ -636,17 +532,6 @@ const TradeList = () => {
</select>
</div>
<div className="filter-section">
<label title="只显示有开仓/平仓订单号的记录,统计与币安一致,避免系统盈利而币安亏损的偏差">
<input
type="checkbox"
checked={reconciledOnly}
onChange={(e) => setReconciledOnly(e.target.checked)}
/>
仅可对账与币安一致
</label>
</div>
<div className="filter-actions">
<button className="btn-primary" onClick={loadData}>
查询
@ -665,42 +550,6 @@ const TradeList = () => {
</>
)}
</div>
{syncResult && (
<div style={{
marginTop: '10px',
padding: '10px',
backgroundColor: syncResult.success ? '#e8f5e9' : '#ffebee',
border: `1px solid ${syncResult.success ? '#4CAF50' : '#f44336'}`,
borderRadius: '4px',
fontSize: '13px',
color: syncResult.success ? '#2e7d32' : '#c62828'
}}>
{syncResult.message}
{syncResult.details && syncResult.success && (
<div style={{ marginTop: '8px', fontSize: '12px', color: '#666' }}>
开仓订单{syncResult.details.open_orders || 0} |
平仓订单{syncResult.details.close_orders || 0} |
更新记录{syncResult.details.updated_trades || 0}
{syncResult.details.created_trades ? ` | 新建记录:${syncResult.details.created_trades}` : ''}
{syncResult.details.entry_order_id_filled ? ` | 补全开仓订单号:${syncResult.details.entry_order_id_filled}` : ''}
{syncResult.details.exit_order_id_filled ? ` | 补全平仓订单号:${syncResult.details.exit_order_id_filled}` : ''}
</div>
)}
<button
onClick={() => setSyncResult(null)}
style={{
float: 'right',
background: 'none',
border: 'none',
color: '#666',
cursor: 'pointer',
fontSize: '16px'
}}
>
×
</button>
</div>
)}
</div>
{
@ -883,12 +732,12 @@ const TradeList = () => {
<div>暂无交易记录</div>
{dataSource === 'binance' && (
<p style={{ marginTop: '10px', fontSize: '13px', color: '#666', maxWidth: '480px' }}>
暂无已同步的币安成交记录数据由定时任务每 3 小时同步存在一定延时请先在服务器运行 scripts/sync_binance_orders.py 或稍后刷新也可切换为本地记录查看本系统 trades
暂无已同步的币安成交记录数据由定时任务每 3 小时同步请稍后刷新或切换为本地记录
</p>
)}
{dataSource === 'local' && reconciledOnly && (
{dataSource === 'local' && (
<p style={{ marginTop: '10px', fontSize: '13px', color: '#666', maxWidth: '420px' }}>
若币安今日有订单但此处为空可先点击右上角同步订单补全开仓/平仓订单号或取消勾选仅可对账查看全部记录
暂无本地 trades 记录
</p>
)}
</div>