fix(backtest): include unrealized PnL in equity curve for accurate MDD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
21in7
2026-03-21 18:26:09 +09:00
parent 0cc5835b3a
commit 0fe87bb366
2 changed files with 34 additions and 6 deletions

View File

@@ -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:

View File

@@ -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']}"