1
This commit is contained in:
parent
777f9ff703
commit
a88e114b4c
|
|
@ -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)
|
||||
|
|
|
|||
23
backend/database/cleanup_non_system_trades.sql
Normal file
23
backend/database/cleanup_non_system_trades.sql
Normal 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;
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
查询
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user