fix: fetch actual PnL from Binance income API on SYNC close detection
When the position monitor detects a missed close via API fallback, it now queries Binance futures_income_history to get the real realized PnL and commission instead of logging zeros. Exit price is estimated from entry price + PnL/quantity. This ensures the dashboard records accurate profit data even when WebSocket events are missed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
36
src/bot.py
36
src/bot.py
@@ -320,20 +320,40 @@ class TradingBot:
|
||||
f"[{self.symbol}] 포지션 불일치 감지: "
|
||||
f"봇={self.current_trade_side}, 바이낸스=포지션 없음 — 상태 동기화"
|
||||
)
|
||||
estimated_pnl = 0.0
|
||||
await self.risk.close_position(self.symbol, estimated_pnl)
|
||||
# Binance income API에서 실제 PnL 조회
|
||||
realized_pnl = 0.0
|
||||
commission = 0.0
|
||||
exit_price = 0.0
|
||||
try:
|
||||
pnl_rows, comm_rows = await self.exchange.get_recent_income(limit=5)
|
||||
if pnl_rows:
|
||||
realized_pnl = float(pnl_rows[-1].get("income", "0"))
|
||||
if comm_rows:
|
||||
commission = abs(float(comm_rows[-1].get("income", "0")))
|
||||
except Exception:
|
||||
pass
|
||||
net_pnl = realized_pnl - commission
|
||||
# exit_price 추정: 진입가 + PnL/수량
|
||||
if self._entry_quantity and self._entry_quantity > 0 and self._entry_price:
|
||||
if self.current_trade_side == "LONG":
|
||||
exit_price = self._entry_price + realized_pnl / self._entry_quantity
|
||||
else:
|
||||
exit_price = self._entry_price - realized_pnl / self._entry_quantity
|
||||
|
||||
await self.risk.close_position(self.symbol, net_pnl)
|
||||
self.notifier.notify_close(
|
||||
symbol=self.symbol,
|
||||
side=self.current_trade_side,
|
||||
close_reason="SYNC",
|
||||
exit_price=0.0,
|
||||
estimated_pnl=0.0,
|
||||
net_pnl=0.0,
|
||||
diff=0.0,
|
||||
exit_price=exit_price,
|
||||
estimated_pnl=realized_pnl,
|
||||
net_pnl=net_pnl,
|
||||
diff=net_pnl - realized_pnl,
|
||||
)
|
||||
logger.info(
|
||||
f"[{self.symbol}] 청산 감지(SYNC): exit=0.0000, "
|
||||
f"rp=+0.0000, commission=0.0000, net_pnl=+0.0000"
|
||||
f"[{self.symbol}] 청산 감지(SYNC): exit={exit_price:.4f}, "
|
||||
f"rp={realized_pnl:+.4f}, commission={commission:.4f}, "
|
||||
f"net_pnl={net_pnl:+.4f}"
|
||||
)
|
||||
self.current_trade_side = None
|
||||
self._entry_price = None
|
||||
|
||||
@@ -193,6 +193,27 @@ class BinanceFuturesClient:
|
||||
except Exception as e:
|
||||
logger.warning(f"Algo 주문 전체 취소 실패 (무시): {e}")
|
||||
|
||||
async def get_recent_income(self, limit: int = 5) -> list[dict]:
|
||||
"""최근 REALIZED_PNL + COMMISSION 내역을 조회한다."""
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
rows = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: self.client.futures_income_history(
|
||||
symbol=self.symbol, incomeType="REALIZED_PNL", limit=limit,
|
||||
),
|
||||
)
|
||||
commissions = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: self.client.futures_income_history(
|
||||
symbol=self.symbol, incomeType="COMMISSION", limit=limit,
|
||||
),
|
||||
)
|
||||
return rows, commissions
|
||||
except Exception as e:
|
||||
logger.warning(f"[{self.symbol}] 수익 내역 조회 실패: {e}")
|
||||
return [], []
|
||||
|
||||
async def get_open_interest(self) -> float | None:
|
||||
"""현재 미결제약정(OI)을 조회한다. 오류 시 None 반환."""
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
Reference in New Issue
Block a user