This commit is contained in:
薇薇安 2026-02-14 17:20:34 +08:00
parent 777f9ff703
commit a88e114b4c
6 changed files with 49 additions and 42 deletions

View File

@ -80,7 +80,6 @@ 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'"),
limit: int = Query(100, ge=1, le=1000, description="返回记录数限制"),
only_system_orders: bool = Query(False, description="仅返回本系统开仓的记录(有开仓订单号),排除同步/手动录入的仓位"),
):
"""
获取交易记录
@ -126,7 +125,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, only_system_orders=only_system_orders)
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, status, trade_type, exit_reason, account_id=account_id)
logger.info(f"查询到 {len(trades)} 条交易记录")
# 格式化交易记录,添加平仓类型的中文显示
@ -167,8 +166,7 @@ async def get_trades(
"end_date": datetime.fromtimestamp(end_timestamp).strftime('%Y-%m-%d %H:%M:%S') if end_timestamp else None,
"period": period,
"symbol": symbol,
"status": status,
"only_system_orders": only_system_orders
"status": status
}
}
@ -186,11 +184,10 @@ 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="交易对筛选"),
only_system_orders: bool = Query(False, description="仅统计本系统开仓的记录"),
):
"""获取交易统计"""
try:
logger.info(f"获取交易统计请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}, only_system_orders={only_system_orders}")
logger.info(f"获取交易统计请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}")
start_timestamp = None
end_timestamp = None
@ -223,7 +220,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, only_system_orders=only_system_orders)
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id)
closed_trades = [t for t in trades if t['status'] == 'closed']
# 辅助函数:计算净盈亏(优先使用 realized_pnl - commission

View File

@ -0,0 +1,23 @@
-- 清理「非交易系统下单」的交易记录(无开仓订单号的记录)
-- 本系统开仓会在成交后保存 entry_order_id无该字段或为 0 的为同步补录/其它来源,可安全删除。
-- 执行前请先备份数据库或至少备份 trades 表。
-- 若表结构较旧、没有 entry_order_id 列,请先执行 add_order_ids.sql 或跳过本脚本。
-- 1) 查看将要删除的记录数(按账号)
SELECT account_id, status, COUNT(*) AS cnt
FROM trades
WHERE entry_order_id IS NULL OR entry_order_id = 0
GROUP BY account_id, status
ORDER BY account_id, status;
-- 2) 查看将要删除的总数
SELECT COUNT(*) AS will_delete FROM trades
WHERE entry_order_id IS NULL OR entry_order_id = 0;
-- 3) 确认无误后执行删除建议先备份mysqldump -u user -p db_name trades > trades_backup.sql
-- DELETE FROM trades
-- WHERE entry_order_id IS NULL OR entry_order_id = 0;
-- 若只清理指定账号,可加上条件,例如:
-- DELETE FROM trades
-- WHERE (entry_order_id IS NULL OR entry_order_id = 0) AND account_id = 1;

View File

@ -700,18 +700,8 @@ class Trade:
)
@staticmethod
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None, only_system_orders: bool = False):
"""获取交易记录
Args:
start_timestamp: 开始时间Unix时间戳秒数可选
end_timestamp: 结束时间Unix时间戳秒数可选
symbol: 交易对可选
status: 状态可选
trade_type: 交易类型可选
exit_reason: 平仓原因可选
only_system_orders: 若为 True仅返回本系统开仓的记录entry_order_id 非空
"""
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None):
"""获取交易记录(仅包含本系统开仓的记录已通过清理脚本维护,不再在查询里筛选)"""
query = "SELECT * FROM trades WHERE 1=1"
params = []
@ -723,9 +713,6 @@ class Trade:
except Exception:
pass
if only_system_orders and _table_has_column("trades", "entry_order_id"):
query += " AND entry_order_id IS NOT NULL AND entry_order_id != 0"
if start_timestamp is not None:
query += " AND created_at >= %s"
params.append(start_timestamp)

View File

@ -20,7 +20,6 @@ const TradeList = () => {
const [useCustomDate, setUseCustomDate] = useState(false)
const [tradeType, setTradeType] = useState('')
const [exitReason, setExitReason] = useState('')
const [onlySystemOrders, setOnlySystemOrders] = useState(false)
useEffect(() => {
loadData()
@ -46,7 +45,6 @@ const TradeList = () => {
if (status) params.status = status
if (tradeType) params.trade_type = tradeType
if (exitReason) params.exit_reason = exitReason
if (onlySystemOrders) params.only_system_orders = true
const [tradesData, statsData] = await Promise.all([
api.getTrades(params),
@ -82,7 +80,6 @@ const TradeList = () => {
setSymbol('')
setStatus('')
setUseCustomDate(false)
setOnlySystemOrders(false)
}
// /便
@ -420,17 +417,6 @@ const TradeList = () => {
</select>
</div>
<div className="filter-section" style={{ alignItems: 'center' }}>
<label title="只显示本策略在币安下单的记录(有开仓订单号),排除从币安同步/手动录入的仓位">
<input
type="checkbox"
checked={onlySystemOrders}
onChange={(e) => setOnlySystemOrders(e.target.checked)}
/>
{' '}仅本系统开仓
</label>
</div>
<div className="filter-actions">
<button className="btn-primary" onClick={loadData}>
查询

View File

@ -18,6 +18,17 @@ except ImportError:
logger = logging.getLogger(__name__)
def _format_exception(e: Exception) -> str:
"""格式化异常用于日志,避免空 message 导致日志无内容"""
if isinstance(e, BinanceAPIException):
code = getattr(e, 'code', getattr(e, 'status_code', ''))
msg = str(e).strip() or getattr(e, 'message', '')
return f"BinanceAPIException(code={code}) {msg}"
name = type(e).__name__
msg = str(e).strip()
return f"{name}: {msg}" if msg else name
class BinanceClient:
"""币安客户端封装类"""
@ -798,14 +809,15 @@ class BinanceClient:
await asyncio.sleep(1)
continue
logger.error(f"获取持仓信息失败: {e}")
logger.error(f"获取持仓信息失败: {_format_exception(e)}")
return []
except Exception as e:
logger.error(f"获取持仓信息失败: {e}")
logger.error(f"获取持仓信息失败: {_format_exception(e)}")
return []
if last_error:
logger.error(f"获取持仓信息最终失败 (已重试 {retries} 次): {last_error}")
err_msg = _format_exception(last_error) if isinstance(last_error, Exception) else str(last_error)
logger.error(f"获取持仓信息最终失败 (已重试 {retries} 次): {err_msg}")
return []
async def get_recent_trades(self, symbol: str, limit: int = 50) -> List[Dict]:
@ -822,7 +834,7 @@ class BinanceClient:
try:
return await self.client.futures_account_trades(symbol=symbol, limit=limit)
except Exception as e:
logger.error(f"获取成交记录失败 {symbol}: {e}")
logger.error(f"获取成交记录失败 {symbol}: {_format_exception(e)}")
return []
async def get_symbol_info(self, symbol: str) -> Optional[Dict]:

View File

@ -898,7 +898,8 @@ class PositionManager:
logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新")
updated = True
except Exception as e:
logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {e}")
err_msg = str(e).strip() or f"{type(e).__name__}"
logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {err_msg}")
# 清理本地记录
await self._stop_position_monitoring(symbol)
@ -2301,7 +2302,8 @@ class PositionManager:
f"PnL={realized_pnl} USDT, 均价={exit_price:.4f}"
)
except Exception as trade_hist_err:
logger.warning(f"{symbol} [状态同步] 获取成交记录失败: {trade_hist_err}")
err_msg = str(trade_hist_err).strip() or type(trade_hist_err).__name__
logger.warning(f"{symbol} [状态同步] 获取成交记录失败: {err_msg}")
# 获取最近的平仓订单reduceOnly=True的订单
import time