수정본
This commit is contained in:
@@ -766,7 +766,12 @@ class KrBacktestEngine:
|
||||
market: str,
|
||||
random_seed: Optional[int],
|
||||
) -> BacktestResult:
|
||||
"""핵심 시뮬레이션 루프 (동기)"""
|
||||
"""핵심 시뮬레이션 루프 (동기)
|
||||
|
||||
[Look-ahead bias 수정]
|
||||
T일 close로 신호(on_bar) 생성 -> T+1일 open + 슬리피지로 체결.
|
||||
마지막 봉 신호는 다음 봉이 없으므로 무시합니다.
|
||||
"""
|
||||
cash = initial_capital
|
||||
positions: Dict[str, Position] = {}
|
||||
all_trades: List[BacktestTrade] = []
|
||||
@@ -784,6 +789,9 @@ class KrBacktestEngine:
|
||||
peak_equity = initial_capital
|
||||
seed_counter = random_seed if random_seed is not None else None
|
||||
|
||||
# 전일 신호를 저장 (T일 close 신호 -> T+1일 open 체결)
|
||||
pending_orders: List[Dict[str, Any]] = []
|
||||
|
||||
for bar_idx in range(len(all_dates)):
|
||||
current_date = all_dates[bar_idx]
|
||||
|
||||
@@ -795,23 +803,18 @@ class KrBacktestEngine:
|
||||
position_value += stock_bars[bar_idx].close_price * pos.quantity
|
||||
total_equity = cash + position_value
|
||||
|
||||
# 전략 호출
|
||||
orders = strategy.on_bar(
|
||||
bar_idx=bar_idx,
|
||||
bars=bars,
|
||||
positions=positions,
|
||||
cash=cash,
|
||||
total_equity=total_equity,
|
||||
params=params,
|
||||
)
|
||||
|
||||
# 주문 실행
|
||||
for order in orders:
|
||||
# -- 1단계: 전일 신호를 오늘 open + 슬리피지로 체결 --
|
||||
for order in pending_orders:
|
||||
action = order["action"]
|
||||
code = order["stock_code"]
|
||||
price = order["price"]
|
||||
quantity = order["quantity"]
|
||||
|
||||
# T+1일 open 가격을 base price로 사용
|
||||
stock_bars = bars.get(code, [])
|
||||
if bar_idx >= len(stock_bars):
|
||||
continue
|
||||
next_open = float(stock_bars[bar_idx].open_price)
|
||||
|
||||
# 슬리피지용 시드
|
||||
if seed_counter is not None:
|
||||
seed_counter += 1
|
||||
@@ -820,7 +823,7 @@ class KrBacktestEngine:
|
||||
slip_seed = None
|
||||
|
||||
if action == "BUY":
|
||||
exec_price = _apply_buy_slippage(price, slip_seed)
|
||||
exec_price = _apply_buy_slippage(next_open, slip_seed)
|
||||
total_cost, commission = _calc_buy_cost(exec_price, quantity)
|
||||
|
||||
if total_cost > cash:
|
||||
@@ -831,7 +834,7 @@ class KrBacktestEngine:
|
||||
total_cost, commission = _calc_buy_cost(exec_price, quantity)
|
||||
|
||||
cash -= total_cost
|
||||
slippage_amount = abs(exec_price - price) * quantity
|
||||
slippage_amount = abs(exec_price - next_open) * quantity
|
||||
|
||||
trade = BacktestTrade(
|
||||
stock_code=code,
|
||||
@@ -859,14 +862,13 @@ class KrBacktestEngine:
|
||||
if pos is None:
|
||||
continue
|
||||
|
||||
sell_pct = order.get("sell_pct", 1.0)
|
||||
sell_qty = min(quantity, pos.quantity)
|
||||
|
||||
exec_price = _apply_sell_slippage(price, slip_seed)
|
||||
exec_price = _apply_sell_slippage(next_open, slip_seed)
|
||||
proceeds, commission, tax = _calc_sell_proceeds(exec_price, sell_qty, market)
|
||||
|
||||
cash += proceeds
|
||||
slippage_amount = abs(price - exec_price) * sell_qty
|
||||
slippage_amount = abs(next_open - exec_price) * sell_qty
|
||||
|
||||
# PnL 계산
|
||||
entry_cost = pos.entry_price * sell_qty * (1 + BUY_COMMISSION_RATE)
|
||||
@@ -899,6 +901,29 @@ class KrBacktestEngine:
|
||||
pos.quantity = remaining_qty
|
||||
pos.remaining_pct = remaining_qty / (pos.quantity + sell_qty)
|
||||
|
||||
pending_orders.clear()
|
||||
|
||||
# -- 2단계: 오늘 close 기준으로 신호 생성 (체결은 내일 open) --
|
||||
is_last_bar = (bar_idx == len(all_dates) - 1)
|
||||
if not is_last_bar:
|
||||
# 체결 이후의 상태로 평가 자산 재계산 (전략에 전달)
|
||||
position_value = 0.0
|
||||
for code, pos in positions.items():
|
||||
stock_bars_eval = bars.get(code, [])
|
||||
if bar_idx < len(stock_bars_eval):
|
||||
position_value += stock_bars_eval[bar_idx].close_price * pos.quantity
|
||||
total_equity = cash + position_value
|
||||
|
||||
orders = strategy.on_bar(
|
||||
bar_idx=bar_idx,
|
||||
bars=bars,
|
||||
positions=positions,
|
||||
cash=cash,
|
||||
total_equity=total_equity,
|
||||
params=params,
|
||||
)
|
||||
pending_orders = list(orders)
|
||||
|
||||
# 일말 평가
|
||||
position_value = 0.0
|
||||
for code, pos in positions.items():
|
||||
|
||||
Reference in New Issue
Block a user