This commit is contained in:
薇薇安 2026-01-14 20:04:07 +08:00
parent b86abf4309
commit d0d40b8f83
2 changed files with 179 additions and 9 deletions

View File

@ -751,9 +751,55 @@ class PositionManager:
# 获取当前所有持仓 # 获取当前所有持仓
positions = await self.client.get_open_positions() positions = await self.client.get_open_positions()
binance_symbols = {p['symbol'] for p in positions}
active_symbols = set(self.active_positions.keys())
logger.info(f"币安持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else ''})")
logger.info(f"本地持仓记录: {len(active_symbols)} 个 ({', '.join(active_symbols) if active_symbols else ''})")
# 为所有币安持仓启动监控即使不在active_positions中可能是手动开仓的
for position in positions: for position in positions:
symbol = position['symbol'] symbol = position['symbol']
if symbol not in self._monitor_tasks and symbol in self.active_positions: if symbol not in self._monitor_tasks:
# 如果不在active_positions中先创建记录
if symbol not in self.active_positions:
logger.warning(f"{symbol} 在币安有持仓但不在本地记录中,可能是手动开仓,尝试创建记录...")
# 这里会通过sync_positions_with_binance来处理但先启动监控
try:
entry_price = position.get('entryPrice', 0)
position_amt = position['positionAmt']
quantity = abs(position_amt)
side = 'BUY' if position_amt > 0 else 'SELL'
# 创建临时记录用于监控
ticker = await self.client.get_ticker_24h(symbol)
current_price = ticker['price'] if ticker else entry_price
stop_loss_price = self.risk_manager.get_stop_loss_price(entry_price, side)
take_profit_price = self.risk_manager.get_take_profit_price(entry_price, side)
position_info = {
'symbol': symbol,
'side': side,
'quantity': quantity,
'entryPrice': entry_price,
'changePercent': 0,
'orderId': None,
'tradeId': None,
'stopLoss': stop_loss_price,
'takeProfit': take_profit_price,
'initialStopLoss': stop_loss_price,
'leverage': position.get('leverage', 10),
'entryReason': 'manual_entry_temp',
'atr': None,
'maxProfit': 0.0,
'trailingStopActivated': False
}
self.active_positions[symbol] = position_info
logger.info(f"{symbol} 已创建临时持仓记录用于监控")
except Exception as e:
logger.error(f"{symbol} 创建临时持仓记录失败: {e}")
await self._start_position_monitoring(symbol) await self._start_position_monitoring(symbol)
logger.info(f"已启动 {len(self._monitor_tasks)} 个持仓的实时监控") logger.info(f"已启动 {len(self._monitor_tasks)} 个持仓的实时监控")
@ -952,20 +998,52 @@ class PositionManager:
# 检查止盈 # 检查止盈
if not should_close: if not should_close:
take_profit = position_info['takeProfit'] take_profit = float(position_info['takeProfit'])
if position_info['side'] == 'BUY' and current_price >= take_profit: # 计算止盈百分比(用于诊断)
should_close = True if position_info['side'] == 'BUY':
exit_reason = 'take_profit' take_profit_pct = ((take_profit - entry_price) / entry_price) * 100
logger.info( else: # SELL
f"{symbol} [实时监控] 触发止盈: {current_price:.4f} >= {take_profit:.4f} " take_profit_pct = ((entry_price - take_profit) / entry_price) * 100
f"(盈亏: {pnl_percent:.2f}%)"
# 每5%盈利记录一次诊断日志(帮助排查问题)
# 使用更宽松的条件,避免因为浮点数精度问题导致日志不输出
if pnl_percent >= 5.0:
# 每5%记录一次,但允许一些容差
should_log = (int(pnl_percent) % 5 == 0) or (pnl_percent >= 10.0 and pnl_percent < 10.5)
if should_log:
trigger_condition = current_price_float >= take_profit if position_info['side'] == 'BUY' else current_price_float <= take_profit
logger.warning(
f"{symbol} [实时监控] 诊断: 盈利{pnl_percent:.2f}% | "
f"当前价: {current_price_float:.4f} | "
f"入场价: {entry_price:.4f} | "
f"止盈价: {take_profit:.4f} ({take_profit_pct:.2f}%) | "
f"方向: {position_info['side']} | "
f"是否触发: {trigger_condition} | "
f"价格差: {abs(current_price_float - take_profit):.4f} | "
f"监控状态: {'运行中' if symbol in self._monitor_tasks else '未启动'}"
) )
elif position_info['side'] == 'SELL' and current_price <= take_profit:
# 如果盈利超过止盈目标但未触发,记录警告
if pnl_percent > take_profit_pct and not trigger_condition:
logger.error(
f"{symbol} [实时监控] ⚠️ 异常: 盈利{pnl_percent:.2f}% > 止盈目标{take_profit_pct:.2f}%,但未触发平仓!"
)
price_diff = current_price_float - take_profit if position_info['side'] == 'BUY' else take_profit - current_price_float
logger.error(f" 当前价: {current_price_float:.4f}, 止盈价: {take_profit:.4f}, 价格差: {price_diff:.4f}")
if position_info['side'] == 'BUY' and current_price_float >= take_profit:
should_close = True should_close = True
exit_reason = 'take_profit' exit_reason = 'take_profit'
logger.info( logger.info(
f"{symbol} [实时监控] 触发止盈: {current_price:.4f} <= {take_profit:.4f} " f"{symbol} [实时监控] 触发止盈: {current_price_float:.4f} >= {take_profit:.4f} "
f"(盈亏: {pnl_percent:.2f}%)" f"(盈亏: {pnl_percent:.2f}%, 止盈目标: {take_profit_pct:.2f}%)"
)
elif position_info['side'] == 'SELL' and current_price_float <= take_profit:
should_close = True
exit_reason = 'take_profit'
logger.info(
f"{symbol} [实时监控] 触发止盈: {current_price_float:.4f} <= {take_profit:.4f} "
f"(盈亏: {pnl_percent:.2f}%, 止盈目标: {take_profit_pct:.2f}%)"
) )
# 如果触发止损止盈,执行平仓 # 如果触发止损止盈,执行平仓
@ -1010,3 +1088,93 @@ class PositionManager:
logger.info(f"{symbol} [自动平仓] ✓ 平仓成功完成") logger.info(f"{symbol} [自动平仓] ✓ 平仓成功完成")
else: else:
logger.error(f"{symbol} [自动平仓] ❌ 平仓失败") logger.error(f"{symbol} [自动平仓] ❌ 平仓失败")
async def diagnose_position(self, symbol: str):
"""
诊断持仓状态用于排查为什么没有自动平仓
Args:
symbol: 交易对
"""
try:
logger.info(f"{symbol} [诊断] 开始诊断持仓状态...")
# 1. 检查是否在active_positions中
if symbol not in self.active_positions:
logger.warning(f"{symbol} [诊断] ❌ 不在本地持仓记录中 (active_positions)")
logger.warning(f" 可能原因: 手动开仓或系统重启后未同步")
logger.warning(f" 解决方案: 等待下次状态同步或手动触发同步")
else:
position_info = self.active_positions[symbol]
logger.info(f"{symbol} [诊断] ✓ 在本地持仓记录中")
logger.info(f" 入场价: {position_info['entryPrice']:.4f}")
logger.info(f" 方向: {position_info['side']}")
logger.info(f" 数量: {position_info['quantity']:.4f}")
logger.info(f" 止损价: {position_info['stopLoss']:.4f}")
logger.info(f" 止盈价: {position_info['takeProfit']:.4f}")
# 2. 检查WebSocket监控状态
if symbol in self._monitor_tasks:
task = self._monitor_tasks[symbol]
if task.done():
logger.warning(f"{symbol} [诊断] ⚠ WebSocket监控任务已结束")
try:
await task # 获取异常信息
except Exception as e:
logger.warning(f" 任务异常: {e}")
else:
logger.info(f"{symbol} [诊断] ✓ WebSocket监控任务运行中")
else:
logger.warning(f"{symbol} [诊断] ❌ 没有WebSocket监控任务")
logger.warning(f" 可能原因: 监控未启动或已停止")
# 3. 获取币安实际持仓
positions = await self.client.get_open_positions()
binance_position = next((p for p in positions if p['symbol'] == symbol), None)
if not binance_position:
logger.warning(f"{symbol} [诊断] ❌ 币安账户中没有持仓")
return
logger.info(f"{symbol} [诊断] ✓ 币安账户中有持仓")
entry_price_binance = binance_position.get('entryPrice', 0)
mark_price = binance_position.get('markPrice', 0)
unrealized_pnl = binance_position.get('unRealizedProfit', 0)
logger.info(f" 币安入场价: {entry_price_binance:.4f}")
logger.info(f" 标记价格: {mark_price:.4f}")
logger.info(f" 未实现盈亏: {unrealized_pnl:.2f} USDT")
# 4. 计算实际盈亏
if symbol in self.active_positions:
position_info = self.active_positions[symbol]
entry_price = float(position_info['entryPrice'])
take_profit = float(position_info['takeProfit'])
if position_info['side'] == 'BUY':
pnl_percent = ((mark_price - entry_price) / entry_price) * 100
take_profit_pct = ((take_profit - entry_price) / entry_price) * 100
should_trigger = mark_price >= take_profit
else: # SELL
pnl_percent = ((entry_price - mark_price) / entry_price) * 100
take_profit_pct = ((entry_price - take_profit) / entry_price) * 100
should_trigger = mark_price <= take_profit
logger.info(f"{symbol} [诊断] 盈亏分析:")
logger.info(f" 当前盈亏: {pnl_percent:.2f}%")
logger.info(f" 止盈目标: {take_profit_pct:.2f}%")
logger.info(f" 当前价: {mark_price:.4f}")
logger.info(f" 止盈价: {take_profit:.4f}")
logger.info(f" 价格差: {abs(mark_price - take_profit):.4f}")
logger.info(f" 应该触发: {should_trigger}")
if pnl_percent > take_profit_pct and not should_trigger:
logger.error(f"{symbol} [诊断] ❌ 异常: 盈亏{pnl_percent:.2f}% > 止盈目标{take_profit_pct:.2f}%,但未触发平仓!")
logger.error(f" 可能原因: 浮点数精度问题或止盈价格计算错误")
logger.info(f"{symbol} [诊断] 诊断完成")
except Exception as e:
logger.error(f"{symbol} [诊断] 诊断失败: {e}")
import traceback
logger.error(f" 错误详情:\n{traceback.format_exc()}")

View File

@ -164,6 +164,8 @@ class TradingStrategy:
# 3. 同步币安实际持仓状态与数据库(定期同步,确保状态一致) # 3. 同步币安实际持仓状态与数据库(定期同步,确保状态一致)
try: try:
await self.position_manager.sync_positions_with_binance() await self.position_manager.sync_positions_with_binance()
# 同步后,确保所有持仓都有监控(包括手动开仓的)
await self.position_manager.start_all_position_monitoring()
except Exception as e: except Exception as e:
logger.warning(f"持仓状态同步失败: {e}") logger.warning(f"持仓状态同步失败: {e}")