feat(trades, database, frontend): 增强交易记录同步与展示功能
在 `trades.py` 中更新了 `include_sync` 参数的默认值为 `True`,以便于订单记录与币安一致,并添加了提示信息以指导用户如何补全缺失的订单号。在 `models.py` 中新增了 `get_trades_missing_entry_order_id` 方法,用于获取缺少 `entry_order_id` 的记录,确保在同步时能够补全数据。前端组件 `StatsDashboard.jsx` 和 `TradeList.jsx` 中相应调整了开仓时间的展示逻辑和无交易记录时的提示信息,提升了用户体验与数据准确性。
This commit is contained in:
parent
22830355c6
commit
9b81832af2
|
|
@ -80,7 +80,7 @@ async def get_trades(
|
|||
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 的历史同步单"),
|
||||
include_sync: bool = Query(True, 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="返回记录数限制"),
|
||||
):
|
||||
|
|
@ -173,6 +173,9 @@ async def get_trades(
|
|||
"reconciled_only": reconciled_only,
|
||||
}
|
||||
}
|
||||
# 仅可对账且无记录时提示:可先同步币安订单补全订单号,或取消「仅可对账」查看全部
|
||||
if reconciled_only and len(formatted_trades) == 0:
|
||||
result["hint"] = "当前没有可对账记录。可尝试:1) 调用「同步币安订单」接口补全开仓/平仓订单号;2) 使用 reconciled_only=false 查看全部记录(含无订单号的补建单)。"
|
||||
|
||||
logger.debug(f"返回交易记录: {len(result['trades'])} 条")
|
||||
return result
|
||||
|
|
@ -189,7 +192,7 @@ async def get_trade_stats(
|
|||
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 的历史同步单"),
|
||||
include_sync: bool = Query(True, description="是否包含 entry_reason 为 sync_recovered 的补建/同步单(默认与订单记录一致)"),
|
||||
reconciled_only: bool = Query(True, description="仅统计可对账记录,与币安一致,避免系统盈利/币安亏损偏差"),
|
||||
):
|
||||
"""获取交易统计(默认按平仓时间统计:今日=今日平仓的盈亏,与订单记录筛选一致)"""
|
||||
|
|
@ -759,19 +762,25 @@ async def sync_trades_from_binance(
|
|||
time_window_start = order_time_sec - 3600
|
||||
time_window_end = order_time_sec + 3600
|
||||
|
||||
trades_no_entry_id = Trade.get_all(
|
||||
trades_in_window = Trade.get_all(
|
||||
account_id=aid,
|
||||
symbol=symbol,
|
||||
start_timestamp=time_window_start,
|
||||
end_timestamp=time_window_end,
|
||||
time_filter="entry", # 使用 entry_time 过滤
|
||||
time_filter="entry",
|
||||
reconciled_only=False,
|
||||
)
|
||||
|
||||
# 过滤出没有 entry_order_id 的记录
|
||||
trades_no_entry_id = [
|
||||
t for t in trades_no_entry_id
|
||||
t for t in trades_in_window
|
||||
if not t.get("entry_order_id") or str(t.get("entry_order_id")).strip() in ("", "0")
|
||||
]
|
||||
# 并入缺少 entry_order_id 的其它记录(含 entry_time 为 NULL 的旧记录),便于补全后「仅可对账」能显示
|
||||
extra = Trade.get_trades_missing_entry_order_id(symbol, aid, limit=50)
|
||||
seen_ids = {t["id"] for t in trades_no_entry_id}
|
||||
for t in extra:
|
||||
if t.get("id") not in seen_ids:
|
||||
trades_no_entry_id.append(t)
|
||||
seen_ids.add(t["id"])
|
||||
|
||||
# 按价格和数量匹配(允许 5% 误差)
|
||||
matched_trade = None
|
||||
|
|
|
|||
|
|
@ -906,6 +906,38 @@ class Trade:
|
|||
(exit_order_id,)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_trades_missing_entry_order_id(symbol: str, account_id: int, limit: int = 50):
|
||||
"""
|
||||
获取该 symbol+account 下缺少 entry_order_id 的记录(用于从币安同步订单时补全)。
|
||||
包含 entry_time 为 NULL 的旧记录,避免「仅可对账」下看不到今日有单却未补全的情况。
|
||||
"""
|
||||
if not symbol or account_id is None:
|
||||
return []
|
||||
try:
|
||||
if not _table_has_column("trades", "entry_order_id"):
|
||||
return []
|
||||
if _table_has_column("trades", "account_id"):
|
||||
return db.execute_query(
|
||||
"""SELECT * FROM trades
|
||||
WHERE account_id = %s AND symbol = %s
|
||||
AND (entry_order_id IS NULL OR entry_order_id = 0 OR entry_order_id = '')
|
||||
ORDER BY id DESC
|
||||
LIMIT %s""",
|
||||
(int(account_id), symbol.strip(), int(limit)),
|
||||
)
|
||||
return db.execute_query(
|
||||
"""SELECT * FROM trades
|
||||
WHERE symbol = %s
|
||||
AND (entry_order_id IS NULL OR entry_order_id = 0 OR entry_order_id = '')
|
||||
ORDER BY id DESC
|
||||
LIMIT %s""",
|
||||
(symbol.strip(), int(limit)),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"get_trades_missing_entry_order_id 失败: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_by_client_order_id(client_order_id, account_id: int = None):
|
||||
"""根据 clientOrderId 获取交易记录(可选按 account_id 隔离)"""
|
||||
|
|
|
|||
|
|
@ -738,9 +738,9 @@ const StatsDashboard = () => {
|
|||
{/* 止损止盈比例 */}
|
||||
|
||||
|
||||
{trade.entry_time && (
|
||||
<div className="entry-time">开仓时间: {formatEntryTime(trade.entry_time)}</div>
|
||||
)}
|
||||
<div className="entry-time">
|
||||
开仓时间: {trade.entry_time ? formatEntryTime(trade.entry_time) : '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="trade-protection-col">
|
||||
<div className="stop-take-info">
|
||||
|
|
|
|||
|
|
@ -110,9 +110,12 @@ const TradeList = () => {
|
|||
|
||||
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} 条新记录` : ''}`,
|
||||
message: `同步完成:共处理 ${result.total_orders || 0} 个订单,更新 ${result.updated_trades || 0} 条记录${result.created_trades ? `,创建 ${result.created_trades} 条新记录` : ''}${backfillHint}`,
|
||||
details: result
|
||||
})
|
||||
// 同步成功后自动刷新数据
|
||||
|
|
@ -810,7 +813,14 @@ const TradeList = () => {
|
|||
|
||||
{
|
||||
trades.length === 0 ? (
|
||||
<div className="no-data">暂无交易记录</div>
|
||||
<div className="no-data">
|
||||
<div>暂无交易记录</div>
|
||||
{reconciledOnly && (
|
||||
<p style={{ marginTop: '10px', fontSize: '13px', color: '#666', maxWidth: '420px' }}>
|
||||
若币安今日有订单但此处为空,可先点击右上角「同步订单」补全开仓/平仓订单号,或取消勾选「仅可对账」查看全部记录。
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 桌面端表格:用横向滚动包裹,避免整页过宽 */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user