This commit is contained in:
薇薇安 2026-01-14 19:07:29 +08:00
parent 8e8a8e633f
commit dee37d3c78
2 changed files with 179 additions and 10 deletions

View File

@ -206,17 +206,20 @@ class PositionManager:
logger.error(f"开仓失败 {symbol}: {e}") logger.error(f"开仓失败 {symbol}: {e}")
return None return None
async def close_position(self, symbol: str) -> bool: async def close_position(self, symbol: str, reason: str = 'manual') -> bool:
""" """
平仓 平仓
Args: Args:
symbol: 交易对 symbol: 交易对
reason: 平仓原因manual, stop_loss, take_profit, trailing_stop, sync
Returns: Returns:
是否成功 是否成功
""" """
try: try:
logger.info(f"{symbol} [平仓] 开始平仓操作 (原因: {reason})")
# 获取当前持仓 # 获取当前持仓
positions = await self.client.get_open_positions() positions = await self.client.get_open_positions()
position = next( position = next(
@ -225,7 +228,43 @@ class PositionManager:
) )
if not position: if not position:
logger.warning(f"{symbol} 没有持仓") logger.warning(f"{symbol} [平仓] 币安账户中没有持仓,可能已被平仓")
# 即使币安没有持仓,也要更新数据库状态
if DB_AVAILABLE and Trade and symbol in self.active_positions:
position_info = self.active_positions[symbol]
trade_id = position_info.get('tradeId')
if trade_id:
try:
logger.info(f"{symbol} [平仓] 更新数据库状态为已平仓 (ID: {trade_id})...")
# 获取当前价格作为平仓价格
ticker = await self.client.get_ticker_24h(symbol)
exit_price = ticker['price'] if ticker else position_info['entryPrice']
entry_price = position_info['entryPrice']
quantity = position_info['quantity']
if position_info['side'] == 'BUY':
pnl = (exit_price - entry_price) * quantity
pnl_percent = ((exit_price - entry_price) / entry_price) * 100
else:
pnl = (entry_price - exit_price) * quantity
pnl_percent = ((entry_price - exit_price) / entry_price) * 100
Trade.update_exit(
trade_id=trade_id,
exit_price=exit_price,
exit_reason=reason,
pnl=pnl,
pnl_percent=pnl_percent
)
logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新")
except Exception as e:
logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {e}")
# 清理本地记录
await self._stop_position_monitoring(symbol)
if symbol in self.active_positions:
del self.active_positions[symbol]
return False return False
# 确定平仓方向(与开仓相反) # 确定平仓方向(与开仓相反)
@ -233,6 +272,11 @@ class PositionManager:
side = 'SELL' if position_amt > 0 else 'BUY' side = 'SELL' if position_amt > 0 else 'BUY'
quantity = abs(position_amt) quantity = abs(position_amt)
logger.info(
f"{symbol} [平仓] 下单信息: {side} {quantity:.4f} @ MARKET "
f"(持仓数量: {position_amt:.4f})"
)
# 平仓 # 平仓
order = await self.client.place_order( order = await self.client.place_order(
symbol=symbol, symbol=symbol,
@ -242,6 +286,7 @@ class PositionManager:
) )
if order: if order:
logger.info(f"{symbol} [平仓] ✓ 平仓订单已提交 (订单ID: {order.get('orderId', 'N/A')})")
# 获取平仓价格 # 获取平仓价格
ticker = await self.client.get_ticker_24h(symbol) ticker = await self.client.get_ticker_24h(symbol)
if not ticker: if not ticker:
@ -269,11 +314,14 @@ class PositionManager:
Trade.update_exit( Trade.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=exit_price, exit_price=exit_price,
exit_reason='manual', exit_reason=reason,
pnl=pnl, pnl=pnl,
pnl_percent=pnl_percent pnl_percent=pnl_percent
) )
logger.info(f"{symbol} 平仓记录已更新到数据库 (盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)") logger.info(
f"{symbol} [平仓] ✓ 数据库记录已更新 "
f"(盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%, 原因: {reason})"
)
except Exception as e: except Exception as e:
logger.error(f"❌ 更新平仓记录到数据库失败: {e}") logger.error(f"❌ 更新平仓记录到数据库失败: {e}")
logger.error(f" 错误类型: {type(e).__name__}") logger.error(f" 错误类型: {type(e).__name__}")
@ -293,13 +341,18 @@ class PositionManager:
if symbol in self.active_positions: if symbol in self.active_positions:
del self.active_positions[symbol] del self.active_positions[symbol]
logger.info(f"平仓成功: {symbol} {side} {quantity}") logger.info(
f"{symbol} [平仓] ✓ 平仓完成: {side} {quantity:.4f} @ {exit_price:.4f} "
f"(原因: {reason})"
)
return True return True
return False return False
except Exception as e: except Exception as e:
logger.error(f"平仓失败 {symbol}: {e}") logger.error(f"{symbol} [平仓] ❌ 平仓失败: {e}")
import traceback
logger.error(f" 错误详情:\n{traceback.format_exc()}")
return False return False
async def check_stop_loss_take_profit(self) -> List[str]: async def check_stop_loss_take_profit(self) -> List[str]:
@ -516,6 +569,98 @@ class PositionManager:
logger.error(f"获取持仓摘要失败: {e}") logger.error(f"获取持仓摘要失败: {e}")
return {} return {}
async def sync_positions_with_binance(self):
"""
同步币安实际持仓状态与数据库状态
检查哪些持仓在数据库中还是open状态但在币安已经不存在了
"""
if not DB_AVAILABLE or not Trade:
logger.debug("数据库不可用,跳过持仓状态同步")
return
try:
logger.info("开始同步币安持仓状态与数据库...")
# 1. 获取币安实际持仓
binance_positions = await self.client.get_open_positions()
binance_symbols = {p['symbol'] for p in binance_positions}
logger.debug(f"币安实际持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else ''})")
# 2. 获取数据库中状态为open的交易记录
db_open_trades = Trade.get_all(status='open')
db_open_symbols = {t['symbol'] for t in db_open_trades}
logger.debug(f"数据库open状态: {len(db_open_symbols)} 个 ({', '.join(db_open_symbols) if db_open_symbols else ''})")
# 3. 找出在数据库中open但在币安已不存在的持仓
missing_in_binance = db_open_symbols - binance_symbols
if missing_in_binance:
logger.warning(
f"发现 {len(missing_in_binance)} 个持仓在数据库中为open状态但在币安已不存在: "
f"{', '.join(missing_in_binance)}"
)
# 4. 更新这些持仓的状态
for symbol in missing_in_binance:
trades = Trade.get_by_symbol(symbol, status='open')
for trade in trades:
trade_id = trade['id']
try:
logger.info(f"{symbol} [状态同步] 更新交易记录状态 (ID: {trade_id})...")
# 获取当前价格作为平仓价格
ticker = await self.client.get_ticker_24h(symbol)
exit_price = ticker['price'] if ticker else trade['entry_price']
# 计算盈亏
entry_price = trade['entry_price']
quantity = trade['quantity']
if trade['side'] == 'BUY':
pnl = (exit_price - entry_price) * quantity
pnl_percent = ((exit_price - entry_price) / entry_price) * 100
else: # SELL
pnl = (entry_price - exit_price) * quantity
pnl_percent = ((entry_price - exit_price) / entry_price) * 100
Trade.update_exit(
trade_id=trade_id,
exit_price=exit_price,
exit_reason='sync', # 标记为同步更新
pnl=pnl,
pnl_percent=pnl_percent
)
logger.info(
f"{symbol} [状态同步] ✓ 已更新 (ID: {trade_id}, "
f"盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)"
)
# 清理本地记录
if symbol in self.active_positions:
await self._stop_position_monitoring(symbol)
del self.active_positions[symbol]
logger.debug(f"{symbol} [状态同步] 已清理本地持仓记录")
except Exception as e:
logger.error(f"{symbol} [状态同步] ❌ 更新失败 (ID: {trade_id}): {e}")
else:
logger.info("✓ 持仓状态同步完成,数据库与币安状态一致")
# 5. 检查币安有但数据库没有记录的持仓(可能是手动开仓的)
missing_in_db = binance_symbols - db_open_symbols
if missing_in_db:
logger.info(
f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有记录: "
f"{', '.join(missing_in_db)} (可能是手动开仓)"
)
logger.info("持仓状态同步完成")
except Exception as e:
logger.error(f"同步持仓状态失败: {e}")
import traceback
logger.error(f"错误详情:\n{traceback.format_exc()}")
async def start_all_position_monitoring(self): async def start_all_position_monitoring(self):
""" """
启动所有持仓的WebSocket实时监控 启动所有持仓的WebSocket实时监控
@ -747,6 +892,15 @@ class PositionManager:
# 如果触发止损止盈,执行平仓 # 如果触发止损止盈,执行平仓
if should_close: if should_close:
logger.info(
f"{symbol} [自动平仓] 开始执行平仓操作 | "
f"原因: {exit_reason} | "
f"入场价: {entry_price:.4f} | "
f"当前价: {current_price:.4f} | "
f"盈亏: {pnl_percent:.2f}% | "
f"数量: {quantity:.4f}"
)
# 更新数据库 # 更新数据库
if DB_AVAILABLE and Trade: if DB_AVAILABLE and Trade:
trade_id = position_info.get('tradeId') trade_id = position_info.get('tradeId')
@ -757,6 +911,7 @@ class PositionManager:
else: # SELL else: # SELL
pnl = (entry_price - current_price) * quantity pnl = (entry_price - current_price) * quantity
logger.info(f"{symbol} [自动平仓] 更新数据库记录 (ID: {trade_id})...")
Trade.update_exit( Trade.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=current_price, exit_price=current_price,
@ -764,8 +919,16 @@ class PositionManager:
pnl=pnl, pnl=pnl,
pnl_percent=pnl_percent pnl_percent=pnl_percent
) )
logger.info(f"{symbol} [自动平仓] ✓ 数据库记录已更新 (盈亏: {pnl:.2f} USDT)")
except Exception as e: except Exception as e:
logger.warning(f"更新{exit_reason}记录失败: {e}") logger.error(f"{symbol} [自动平仓] ❌ 更新数据库记录失败: {e}")
import traceback
logger.error(f" 错误详情:\n{traceback.format_exc()}")
# 执行平仓(注意:这里会停止监控,所以先更新数据库) # 执行平仓(注意:这里会停止监控,所以先更新数据库)
await self.close_position(symbol) logger.info(f"{symbol} [自动平仓] 正在执行平仓订单...")
success = await self.close_position(symbol, reason=exit_reason)
if success:
logger.info(f"{symbol} [自动平仓] ✓ 平仓成功完成")
else:
logger.error(f"{symbol} [自动平仓] ❌ 平仓失败")

View File

@ -154,13 +154,19 @@ class TradingStrategy:
# 避免同时处理太多交易对 # 避免同时处理太多交易对
await asyncio.sleep(1) await asyncio.sleep(1)
# 3. 检查止损止盈作为备用检查WebSocket实时监控是主要方式 # 3. 同步币安实际持仓状态与数据库(定期同步,确保状态一致)
try:
await self.position_manager.sync_positions_with_binance()
except Exception as e:
logger.warning(f"持仓状态同步失败: {e}")
# 4. 检查止损止盈作为备用检查WebSocket实时监控是主要方式
# 注意如果启用了WebSocket实时监控这里主要是作为备用检查 # 注意如果启用了WebSocket实时监控这里主要是作为备用检查
closed = await self.position_manager.check_stop_loss_take_profit() closed = await self.position_manager.check_stop_loss_take_profit()
if closed: if closed:
logger.info(f"定时检查触发平仓: {', '.join(closed)}") logger.info(f"定时检查触发平仓: {', '.join(closed)}")
# 4. 打印持仓摘要并记录账户快照 # 5. 打印持仓摘要并记录账户快照
summary = await self.position_manager.get_position_summary() summary = await self.position_manager.get_position_summary()
if summary: if summary:
logger.info( logger.info(