diff --git a/src/backtester.py b/src/backtester.py index e5577db..0a2d563 100644 --- a/src/backtester.py +++ b/src/backtester.py @@ -335,6 +335,7 @@ class Backtester: logger.info(f"총 이벤트: {len(events):,}개") # 메인 루프 + latest_prices: dict[str, float] = {} for ts, sym, candle_idx in events: date_str = str(ts.date()) self.risk.new_day(date_str) @@ -342,9 +343,10 @@ class Backtester: df_ind = all_indicators[sym] signal = all_signals[sym][candle_idx] row = df_ind.iloc[candle_idx] + latest_prices[sym] = float(row["close"]) # 에퀴티 기록 - self._record_equity(ts) + self._record_equity(ts, current_prices=latest_prices) # 1) 일일 손실 체크 if not self.risk.is_trading_allowed(): @@ -568,12 +570,15 @@ class Backtester: } self.trades.append(trade) - def _record_equity(self, ts: pd.Timestamp): - # 미실현 PnL 포함 에퀴티 + def _record_equity(self, ts: pd.Timestamp, current_prices: dict[str, float] | None = None): unrealized = 0.0 - for pos in self.positions.values(): - # 에퀴티 기록 시점에는 현재가를 알 수 없으므로 entry_price 기준으로 0 처리 - pass + for sym, pos in self.positions.items(): + price = (current_prices or {}).get(sym) + if price is not None: + if pos.side == "LONG": + unrealized += (price - pos.entry_price) * pos.quantity + else: + unrealized += (pos.entry_price - price) * pos.quantity equity = self.balance + unrealized self.equity_curve.append({"timestamp": str(ts), "equity": round(equity, 4)}) if equity > self._peak_equity: diff --git a/tests/test_ml_pipeline_fixes.py b/tests/test_ml_pipeline_fixes.py index 3405e3f..fb7f0f7 100644 --- a/tests/test_ml_pipeline_fixes.py +++ b/tests/test_ml_pipeline_fixes.py @@ -54,3 +54,26 @@ def test_default_sltp_backward_compatible(signal_df): if len(r_default) > 0: assert len(r_default) == len(r_explicit) assert (r_default["label"].values == r_explicit["label"].values).all() + + +def test_equity_curve_includes_unrealized_pnl(): + """에퀴티 커브에 미실현 PnL이 반영되어야 한다.""" + from src.backtester import Backtester, BacktestConfig, Position + import pandas as pd + + cfg = BacktestConfig(symbols=["TEST"], initial_balance=1000.0) + bt = Backtester.__new__(Backtester) + bt.cfg = cfg + bt.balance = 1000.0 + bt._peak_equity = 1000.0 + bt.equity_curve = [] + bt.positions = {"TEST": Position( + symbol="TEST", side="LONG", entry_price=100.0, + quantity=10.0, sl=95.0, tp=110.0, + entry_time=pd.Timestamp("2026-01-01"), entry_fee=0.4, + )} + + bt._record_equity(pd.Timestamp("2026-01-01 00:15:00"), current_prices={"TEST": 105.0}) + + last = bt.equity_curve[-1] + assert last["equity"] == 1050.0, f"Expected 1050.0 (1000+50), got {last['equity']}"