feat: add entry state tracking and _on_position_closed callback

- __init__에 _entry_price, _entry_quantity 상태 변수 추가 (None 초기화)
- _open_position()에서 current_trade_side 저장 직후 진입가/수량 저장
- _calc_estimated_pnl() 헬퍼: LONG/SHORT 방향별 예상 PnL 계산
- _on_position_closed() 콜백: UDS 청산 감지 시 PnL 기록·알림·상태 초기화

Made-with: Cursor
This commit is contained in:
21in7
2026-03-02 16:21:59 +09:00
parent 22f1debb3d
commit c4f806fc35

View File

@@ -19,6 +19,8 @@ class TradingBot:
self.risk = RiskManager(config)
self.ml_filter = MLFilter()
self.current_trade_side: str | None = None # "LONG" | "SHORT"
self._entry_price: float | None = None
self._entry_quantity: float | None = None
self._prev_oi: float | None = None # OI 변화율 계산용 이전 값
self.stream = MultiSymbolStream(
symbols=[config.symbol, "BTCUSDT", "ETHUSDT"],
@@ -152,6 +154,8 @@ class TradingBot:
}
self.current_trade_side = signal
self._entry_price = price
self._entry_quantity = quantity
self.notifier.notify_open(
symbol=self.config.symbol,
side=signal,
@@ -183,6 +187,46 @@ class TradingBot:
reduce_only=True,
)
def _calc_estimated_pnl(self, exit_price: float) -> float:
"""진입가·수량 기반 예상 PnL 계산 (수수료 미반영)."""
if self._entry_price is None or self._entry_quantity is None:
return 0.0
if self.current_trade_side == "LONG":
return (exit_price - self._entry_price) * self._entry_quantity
return (self._entry_price - exit_price) * self._entry_quantity
async def _on_position_closed(
self,
net_pnl: float,
close_reason: str,
exit_price: float,
) -> None:
"""User Data Stream에서 청산 감지 시 호출되는 콜백."""
estimated_pnl = self._calc_estimated_pnl(exit_price)
diff = net_pnl - estimated_pnl
self.risk.record_pnl(net_pnl)
self.notifier.notify_close(
symbol=self.config.symbol,
side=self.current_trade_side or "UNKNOWN",
close_reason=close_reason,
exit_price=exit_price,
estimated_pnl=estimated_pnl,
net_pnl=net_pnl,
diff=diff,
)
logger.success(
f"포지션 청산({close_reason}): 예상={estimated_pnl:+.4f}, "
f"순수익={net_pnl:+.4f}, 차이={diff:+.4f} USDT"
)
# Flat 상태로 초기화
self.current_trade_side = None
self._entry_price = None
self._entry_quantity = None
async def _close_position(self, position: dict):
"""포지션 청산 주문만 실행한다. PnL 기록/알림은 _on_position_closed 콜백이 담당."""
amt = abs(float(position["positionAmt"]))