feat(trades): 添加时间筛选功能以优化交易记录查询
在 `trades.py` 中新增 `time_filter` 参数,允许用户按平仓时间或开仓时间筛选交易记录。更新 `Trade.get_all` 方法以支持该功能,并调整查询逻辑以符合新的时间筛选需求。同时,前端组件 `TradeList.jsx` 也进行了相应更新,增加了时间筛选按钮,提升了用户体验和数据查询的灵活性。
This commit is contained in:
parent
3a2536ae96
commit
42480ef886
|
|
@ -79,6 +79,7 @@ async def get_trades(
|
|||
trade_type: Optional[str] = Query(None, description="交易类型筛选: 'buy', 'sell'"),
|
||||
exit_reason: Optional[str] = Query(None, description="平仓原因筛选: 'stop_loss', 'take_profit', 'trailing_stop', 'manual', 'sync'"),
|
||||
status: Optional[str] = Query(None, description="状态筛选: 'open', 'closed', 'cancelled'"),
|
||||
time_filter: str = Query("exit", description="时间范围按哪种时间筛选: 'exit'(按平仓时间,今日=今天平掉的单+今天开的未平仓), 'entry'(按开仓时间), 'both'(原逻辑)"),
|
||||
include_sync: bool = Query(False, description="是否包含 entry_reason 为 sync_recovered 的历史同步单"),
|
||||
reconciled_only: bool = Query(True, description="仅返回可对账记录(有 entry_order_id,已平仓的还有 exit_order_id),与币安一致,统计真实"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="返回记录数限制"),
|
||||
|
|
@ -90,7 +91,7 @@ async def get_trades(
|
|||
1. 快速时间段筛选:使用 period 参数 ('1d', '7d', '30d', 'today', 'week', 'month')
|
||||
2. 自定义时间段筛选:使用 start_date 和 end_date 参数(会转换为Unix时间戳)
|
||||
|
||||
如果同时提供了 period 和 start_date/end_date,period 优先级更高
|
||||
默认按平仓时间(time_filter=exit):选「今天」= 今天平掉的单 + 今天开的未平仓,更符合直觉。
|
||||
"""
|
||||
try:
|
||||
logger.info(f"获取交易记录请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}, status={status}, limit={limit}, trade_type={trade_type}, exit_reason={exit_reason}")
|
||||
|
|
@ -127,7 +128,7 @@ async def get_trades(
|
|||
except ValueError:
|
||||
logger.warning(f"无效的结束日期格式: {end_date}")
|
||||
|
||||
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, status, trade_type, exit_reason, account_id=account_id)
|
||||
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, status, trade_type, exit_reason, account_id=account_id, time_filter=time_filter or "exit")
|
||||
if not include_sync:
|
||||
trades = [
|
||||
t for t in trades
|
||||
|
|
@ -208,10 +209,11 @@ async def get_trade_stats(
|
|||
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
||||
period: Optional[str] = Query(None, description="快速时间段筛选: '1d', '7d', '30d', 'today', 'week', 'month'"),
|
||||
symbol: Optional[str] = Query(None, description="交易对筛选"),
|
||||
time_filter: str = Query("exit", description="时间范围按哪种时间: 'exit'(按平仓时间,今日统计=今日平仓的盈亏), 'entry'(按开仓时间), 'both'"),
|
||||
include_sync: bool = Query(False, description="是否包含 entry_reason 为 sync_recovered 的历史同步单"),
|
||||
reconciled_only: bool = Query(True, description="仅统计可对账记录,与币安一致,避免系统盈利/币安亏损偏差"),
|
||||
):
|
||||
"""获取交易统计(默认仅统计可对账记录,保证与币安一致)"""
|
||||
"""获取交易统计(默认按平仓时间统计:今日=今日平仓的盈亏,与订单记录筛选一致)"""
|
||||
try:
|
||||
logger.info(f"获取交易统计请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}")
|
||||
|
||||
|
|
@ -246,7 +248,7 @@ async def get_trade_stats(
|
|||
except ValueError:
|
||||
logger.warning(f"无效的结束日期格式: {end_date}")
|
||||
|
||||
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id)
|
||||
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id, time_filter=time_filter or "exit")
|
||||
if not include_sync:
|
||||
trades = [
|
||||
t for t in trades
|
||||
|
|
|
|||
|
|
@ -936,8 +936,14 @@ class Trade:
|
|||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None):
|
||||
"""获取交易记录(仅包含本系统开仓的记录已通过清理脚本维护,不再在查询里筛选)"""
|
||||
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None, time_filter: str = "exit"):
|
||||
"""
|
||||
获取交易记录。
|
||||
time_filter: 时间范围按哪种时间筛选
|
||||
- "exit": 按平仓时间(已平仓用 exit_time,未平仓用 entry_time)。选「今天」= 今天平掉的单 + 今天开的未平仓,更符合直觉。
|
||||
- "entry": 按开仓时间。
|
||||
- "both": 原逻辑,COALESCE(exit_time, entry_time)。
|
||||
"""
|
||||
query = "SELECT * FROM trades WHERE 1=1"
|
||||
params = []
|
||||
|
||||
|
|
@ -949,12 +955,38 @@ class Trade:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
if start_timestamp is not None:
|
||||
query += " AND COALESCE(exit_time, entry_time) >= %s"
|
||||
params.append(start_timestamp)
|
||||
if end_timestamp is not None:
|
||||
query += " AND COALESCE(exit_time, entry_time) <= %s"
|
||||
params.append(end_timestamp)
|
||||
if start_timestamp is not None and end_timestamp is not None:
|
||||
if time_filter == "exit":
|
||||
# 按平仓时间:已平仓看 exit_time,未平仓看 entry_time
|
||||
query += " AND ((status = 'closed' AND exit_time >= %s AND exit_time <= %s) OR (status != 'closed' AND entry_time >= %s AND entry_time <= %s))"
|
||||
params.extend([start_timestamp, end_timestamp, start_timestamp, end_timestamp])
|
||||
elif time_filter == "entry":
|
||||
query += " AND entry_time >= %s AND entry_time <= %s"
|
||||
params.extend([start_timestamp, end_timestamp])
|
||||
else:
|
||||
query += " AND COALESCE(exit_time, entry_time) >= %s AND COALESCE(exit_time, entry_time) <= %s"
|
||||
params.extend([start_timestamp, end_timestamp])
|
||||
elif start_timestamp is not None:
|
||||
if time_filter == "exit":
|
||||
query += " AND ((status = 'closed' AND exit_time >= %s) OR (status != 'closed' AND entry_time >= %s))"
|
||||
params.extend([start_timestamp, start_timestamp])
|
||||
elif time_filter == "entry":
|
||||
query += " AND entry_time >= %s"
|
||||
params.append(start_timestamp)
|
||||
else:
|
||||
query += " AND COALESCE(exit_time, entry_time) >= %s"
|
||||
params.append(start_timestamp)
|
||||
elif end_timestamp is not None:
|
||||
if time_filter == "exit":
|
||||
query += " AND ((status = 'closed' AND exit_time <= %s) OR (status != 'closed' AND entry_time <= %s))"
|
||||
params.extend([end_timestamp, end_timestamp])
|
||||
elif time_filter == "entry":
|
||||
query += " AND entry_time <= %s"
|
||||
params.append(end_timestamp)
|
||||
else:
|
||||
query += " AND COALESCE(exit_time, entry_time) <= %s"
|
||||
params.append(end_timestamp)
|
||||
|
||||
if symbol:
|
||||
query += " AND symbol = %s"
|
||||
params.append(symbol)
|
||||
|
|
@ -968,10 +1000,9 @@ class Trade:
|
|||
query += " AND exit_reason = %s"
|
||||
params.append(exit_reason)
|
||||
|
||||
# 按平仓时间倒序排序,如果没有平仓时间则按入场时间倒序
|
||||
# query += " ORDER BY COALESCE(exit_time, entry_time) DESC, entry_time DESC"
|
||||
query += " ORDER BY id DESC"
|
||||
logger.info(f"查询交易记录: {query}, {params}")
|
||||
# 按平仓时间倒序(已平仓的按 exit_time,未平仓的按 entry_time)
|
||||
query += " ORDER BY COALESCE(exit_time, entry_time) DESC, id DESC"
|
||||
logger.info(f"查询交易记录: time_filter={time_filter}, {query}, {params}")
|
||||
result = db.execute_query(query, params)
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@ const TradeList = () => {
|
|||
const [tradeType, setTradeType] = useState('')
|
||||
const [exitReason, setExitReason] = useState('')
|
||||
const [reconciledOnly, setReconciledOnly] = useState(true) // 默认仅可对账,与币安一致
|
||||
const [timeFilter, setTimeFilter] = useState('exit') // 'exit' 按平仓时间(今天=今天平掉的单), 'entry' 按开仓时间
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [accountId, reconciledOnly]) // accountId 或「仅可对账」变化时重新加载
|
||||
}, [accountId, reconciledOnly, timeFilter]) // accountId / 仅可对账 / 时间筛选方式 变化时重新加载
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true)
|
||||
|
|
@ -47,6 +48,7 @@ const TradeList = () => {
|
|||
if (tradeType) params.trade_type = tradeType
|
||||
if (exitReason) params.exit_reason = exitReason
|
||||
params.reconciled_only = reconciledOnly
|
||||
params.time_filter = timeFilter || 'exit'
|
||||
|
||||
const [tradesData, statsData] = await Promise.all([
|
||||
api.getTrades(params),
|
||||
|
|
@ -311,11 +313,30 @@ const TradeList = () => {
|
|||
<div className="trade-list">
|
||||
<h2>交易记录</h2>
|
||||
<p style={{ color: '#666', fontSize: '14px', marginTop: '-10px', marginBottom: '20px' }}>
|
||||
说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。
|
||||
说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。默认「按平仓时间」:选「今天」= 今天平掉的单 + 今天开的未平仓。
|
||||
</p>
|
||||
|
||||
{/* 筛选面板 */}
|
||||
<div className="filter-panel">
|
||||
<div className="filter-section">
|
||||
<label>时间依据:</label>
|
||||
<div className="period-buttons">
|
||||
<button
|
||||
className={timeFilter === 'exit' ? 'active' : ''}
|
||||
onClick={() => setTimeFilter('exit')}
|
||||
title="选「今天」= 今天平仓的已平仓单 + 今天开仓的未平仓单"
|
||||
>
|
||||
按平仓时间
|
||||
</button>
|
||||
<button
|
||||
className={timeFilter === 'entry' ? 'active' : ''}
|
||||
onClick={() => setTimeFilter('entry')}
|
||||
title="选「今天」= 今天开仓的(含未平仓)"
|
||||
>
|
||||
按开仓时间
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="filter-section">
|
||||
<label>快速筛选:</label>
|
||||
<div className="period-buttons">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user