This commit is contained in:
薇薇安 2026-01-16 13:11:49 +08:00
parent f737c32ea2
commit bb1490b909
6 changed files with 133 additions and 25 deletions

View File

@ -276,7 +276,13 @@ async def sync_trades_from_binance(
try:
if reduce_only:
# 这是平仓订单,更新数据库中的对应记录
# 这是平仓订单
# 首先检查是否已经通过订单号同步过(避免重复)
existing_trade = Trade.get_by_exit_order_id(order_id)
if existing_trade:
logger.debug(f"订单 {order_id} 已同步过,跳过")
continue
# 查找数据库中该交易对的open状态记录
open_trades = Trade.get_by_symbol(symbol, status='open')
if open_trades:
@ -298,30 +304,36 @@ async def sync_trades_from_binance(
pnl = (entry_price - avg_price) * actual_quantity
pnl_percent = ((entry_price - avg_price) / entry_price) * 100
# 更新数据库
# 更新数据库(包含订单号)
Trade.update_exit(
trade_id=trade_id,
exit_price=avg_price,
exit_reason='sync',
pnl=pnl,
pnl_percent=pnl_percent
pnl_percent=pnl_percent,
exit_order_id=order_id # 保存订单号,确保唯一性
)
updated_count += 1
logger.debug(f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 成交价: {avg_price:.4f})")
logger.debug(f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, 成交价: {avg_price:.4f})")
else:
# 这是开仓订单,检查数据库中是否已存在
# 这里可以添加逻辑来创建缺失的开仓记录
# 但为了简化,暂时只处理平仓订单
pass
# 这是开仓订单,检查数据库中是否已存在(通过订单号)
existing_trade = Trade.get_by_entry_order_id(order_id)
if not existing_trade:
# 如果不存在,可以创建新记录(但需要更多信息,暂时跳过)
logger.debug(f"发现新的开仓订单 {order_id},但缺少必要信息,跳过创建")
else:
logger.debug(f"开仓订单 {order_id} 已存在,跳过")
except Exception as e:
logger.warning(f"同步订单失败 {symbol} (订单ID: {order_id}): {e}")
continue
result = {
"success": True,
"message": f"同步完成:更新了 {updated_count} 条平仓记录",
"message": f"同步完成:更新了 {updated_count} 条平仓记录(基于订单号匹配,确保唯一性)",
"total_orders": len(all_orders),
"updated_trades": updated_count
"updated_trades": updated_count,
"close_orders": len([o for o in all_orders if o.get('reduceOnly', False)]),
"open_orders": len([o for o in all_orders if not o.get('reduceOnly', False)])
}
logger.info(f"✓ 同步完成:处理了 {len(all_orders)} 个订单,更新了 {updated_count} 条记录")

View File

@ -0,0 +1,16 @@
-- 添加币安订单号字段,用于与币安订单同步和对账
USE `auto_trade_sys`;
-- 添加开仓订单号字段币安开仓订单ID
ALTER TABLE `trades`
ADD COLUMN `entry_order_id` BIGINT UNIQUE COMMENT '币安开仓订单号(唯一)' AFTER `leverage`;
-- 添加平仓订单号字段币安平仓订单ID
ALTER TABLE `trades`
ADD COLUMN `exit_order_id` BIGINT UNIQUE COMMENT '币安平仓订单号(唯一)' AFTER `entry_order_id`;
-- 添加索引以便快速查询
ALTER TABLE `trades`
ADD INDEX `idx_entry_order_id` (`entry_order_id`),
ADD INDEX `idx_exit_order_id` (`exit_order_id`);

View File

@ -88,27 +88,63 @@ class Trade:
"""交易记录模型"""
@staticmethod
def create(symbol, side, quantity, entry_price, leverage=10, entry_reason=None):
"""创建交易记录(使用北京时间)"""
def create(symbol, side, quantity, entry_price, leverage=10, entry_reason=None, entry_order_id=None):
"""创建交易记录(使用北京时间)
Args:
symbol: 交易对
side: 方向
quantity: 数量
entry_price: 入场价
leverage: 杠杆
entry_reason: 入场原因
entry_order_id: 币安开仓订单号可选用于对账
"""
entry_time = get_beijing_time()
db.execute_update(
"""INSERT INTO trades
(symbol, side, quantity, entry_price, leverage, entry_reason, status, entry_time)
VALUES (%s, %s, %s, %s, %s, %s, 'open', %s)""",
(symbol, side, quantity, entry_price, leverage, entry_reason, entry_time)
(symbol, side, quantity, entry_price, leverage, entry_reason, status, entry_time, entry_order_id)
VALUES (%s, %s, %s, %s, %s, %s, 'open', %s, %s)""",
(symbol, side, quantity, entry_price, leverage, entry_reason, entry_time, entry_order_id)
)
return db.execute_one("SELECT LAST_INSERT_ID() as id")['id']
@staticmethod
def update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent):
"""更新平仓信息(使用北京时间)"""
def update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent, exit_order_id=None):
"""更新平仓信息(使用北京时间)
Args:
trade_id: 交易记录ID
exit_price: 出场价
exit_reason: 平仓原因
pnl: 盈亏
pnl_percent: 盈亏百分比
exit_order_id: 币安平仓订单号可选用于对账
"""
exit_time = get_beijing_time()
db.execute_update(
"""UPDATE trades
SET exit_price = %s, exit_time = %s,
exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed'
exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed',
exit_order_id = %s
WHERE id = %s""",
(exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id)
(exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id, trade_id)
)
@staticmethod
def get_by_entry_order_id(entry_order_id):
"""根据开仓订单号获取交易记录"""
return db.execute_one(
"SELECT * FROM trades WHERE entry_order_id = %s",
(entry_order_id,)
)
@staticmethod
def get_by_exit_order_id(exit_order_id):
"""根据平仓订单号获取交易记录"""
return db.execute_one(
"SELECT * FROM trades WHERE exit_order_id = %s",
(exit_order_id,)
)
@staticmethod

View File

@ -365,6 +365,12 @@
color: #2c3e50;
}
.order-id {
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: #666;
}
.trade-card-footer {
display: flex;
justify-content: space-between;

View File

@ -250,6 +250,8 @@ const TradeList = () => {
<th>盈亏比例</th>
<th>状态</th>
<th>平仓类型</th>
<th>开仓订单号</th>
<th>平仓订单号</th>
<th>入场时间</th>
<th>平仓时间</th>
</tr>
@ -304,6 +306,8 @@ const TradeList = () => {
<span className={`status ${trade.status}`}>{trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'}</span>
</td>
<td>{trade.exit_reason_display || '-'}</td>
<td className="order-id">{trade.entry_order_id || '-'}</td>
<td className="order-id">{trade.exit_order_id || '-'}</td>
<td>{formatTime(trade.entry_time)}</td>
<td>{trade.exit_time ? formatTime(trade.exit_time) : '-'}</td>
</tr>
@ -385,6 +389,18 @@ const TradeList = () => {
<span className="trade-card-value">{trade.exit_reason_display}</span>
</div>
)}
{trade.entry_order_id && (
<div className="trade-card-field">
<span className="trade-card-label">开仓订单号</span>
<span className="trade-card-value order-id">{trade.entry_order_id}</span>
</div>
)}
{trade.exit_order_id && (
<div className="trade-card-field">
<span className="trade-card-label">平仓订单号</span>
<span className="trade-card-value order-id">{trade.exit_order_id}</span>
</div>
)}
</div>
<div className="trade-card-footer">
<div className="trade-time-item">

View File

@ -179,6 +179,11 @@ class PositionManager:
)
if order:
# 获取开仓订单号
entry_order_id = order.get('orderId')
if entry_order_id:
logger.info(f"{symbol} [开仓] 币安订单号: {entry_order_id}")
# 记录到数据库
trade_id = None
if DB_AVAILABLE and Trade:
@ -190,9 +195,10 @@ class PositionManager:
quantity=quantity,
entry_price=entry_price,
leverage=leverage,
entry_reason=entry_reason
entry_reason=entry_reason,
entry_order_id=entry_order_id # 保存币安订单号
)
logger.info(f"{symbol} 交易记录已保存到数据库 (ID: {trade_id})")
logger.info(f"{symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id})")
except Exception as e:
logger.error(f"❌ 保存交易记录到数据库失败: {e}")
logger.error(f" 错误类型: {type(e).__name__}")
@ -299,12 +305,14 @@ class PositionManager:
pnl = (entry_price - exit_price) * quantity
pnl_percent = ((entry_price - exit_price) / entry_price) * 100
# 同步平仓时没有订单号设为None
Trade.update_exit(
trade_id=trade_id,
exit_price=exit_price,
exit_reason=reason,
pnl=pnl,
pnl_percent=pnl_percent
pnl_percent=pnl_percent,
exit_order_id=None # 同步平仓时没有订单号
)
logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新")
except Exception as e:
@ -415,12 +423,18 @@ class PositionManager:
pnl = (entry_price - exit_price_float) * quantity_float
pnl_percent = ((entry_price - exit_price_float) / entry_price) * 100
# 获取平仓订单号
exit_order_id = order.get('orderId')
if exit_order_id:
logger.info(f"{symbol} [平仓] 币安订单号: {exit_order_id}")
Trade.update_exit(
trade_id=trade_id,
exit_price=exit_price_float,
exit_reason=reason,
pnl=pnl,
pnl_percent=pnl_percent
pnl_percent=pnl_percent,
exit_order_id=exit_order_id # 保存币安平仓订单号
)
logger.info(
f"{symbol} [平仓] ✓ 数据库记录已更新 "
@ -916,12 +930,20 @@ class PositionManager:
pnl = (entry_price - exit_price) * quantity
pnl_percent = ((entry_price - exit_price) / entry_price) * 100
# 从历史订单中获取平仓订单号
exit_order_id = None
if close_orders:
exit_order_id = close_orders[0].get('orderId')
if exit_order_id:
logger.info(f"{symbol} [状态同步] 找到平仓订单号: {exit_order_id}")
Trade.update_exit(
trade_id=trade_id,
exit_price=exit_price,
exit_reason='sync', # 标记为同步更新
pnl=pnl,
pnl_percent=pnl_percent
pnl_percent=pnl_percent,
exit_order_id=exit_order_id # 保存币安平仓订单号
)
logger.info(