fix: backtest force-closes open positions at end + uses real market prices for exits
This commit is contained in:
@@ -212,7 +212,7 @@ class BacktestReplay:
|
||||
)
|
||||
|
||||
# Simulate simple exit logic: close positions held > 5 days
|
||||
# (simplified — real engine uses stop-loss/take-profit)
|
||||
# or use actual market price if available
|
||||
tickers_to_close = []
|
||||
for ticker, pos_info in simulated_positions.items():
|
||||
hold_days = (current_date - pos_info["entry_date"]).days
|
||||
@@ -221,8 +221,8 @@ class BacktestReplay:
|
||||
|
||||
for ticker in tickers_to_close:
|
||||
pos_info = simulated_positions.pop(ticker)
|
||||
# Simulate a small random-ish exit based on entry price
|
||||
exit_price = pos_info["entry_price"] * 1.01 # simplified
|
||||
# Use actual market price if available, otherwise estimate
|
||||
exit_price = company_prices.get(ticker, pos_info["entry_price"] * 1.01)
|
||||
qty = pos_info["quantity"]
|
||||
pnl = (exit_price - pos_info["entry_price"]) * qty
|
||||
pnl_pct = (
|
||||
@@ -253,10 +253,10 @@ class BacktestReplay:
|
||||
0, portfolio_state.open_position_count - 1
|
||||
)
|
||||
|
||||
# Compute daily portfolio value
|
||||
# Compute daily portfolio value using latest market prices
|
||||
positions_value = sum(
|
||||
p["entry_price"] * p["quantity"]
|
||||
for p in simulated_positions.values()
|
||||
company_prices.get(t, p["entry_price"]) * p["quantity"]
|
||||
for t, p in simulated_positions.items()
|
||||
)
|
||||
current_value = portfolio_state.active_pool + positions_value
|
||||
portfolio_state.total_value = current_value
|
||||
@@ -277,6 +277,42 @@ class BacktestReplay:
|
||||
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
# Force-close any remaining open positions at end of backtest
|
||||
# using the latest available market prices
|
||||
for ticker in list(simulated_positions.keys()):
|
||||
pos_info = simulated_positions.pop(ticker)
|
||||
exit_price = company_prices.get(ticker, pos_info["entry_price"])
|
||||
qty = pos_info["quantity"]
|
||||
pnl = (exit_price - pos_info["entry_price"]) * qty
|
||||
pnl_pct = (
|
||||
(exit_price - pos_info["entry_price"]) / pos_info["entry_price"]
|
||||
if pos_info["entry_price"] > 0
|
||||
else 0.0
|
||||
)
|
||||
hold_duration = timedelta(
|
||||
days=(config.end_date - pos_info["entry_date"]).days
|
||||
)
|
||||
|
||||
trade = ClosedTrade(
|
||||
ticker=ticker,
|
||||
entry_price=pos_info["entry_price"],
|
||||
exit_price=exit_price,
|
||||
quantity=qty,
|
||||
pnl=pnl,
|
||||
pnl_pct=pnl_pct,
|
||||
hold_duration=hold_duration,
|
||||
recommendation_id=pos_info.get("recommendation_id"),
|
||||
)
|
||||
closed_trades.append(trade)
|
||||
trade_log.append(self._perf.compute_trade_metrics(trade))
|
||||
portfolio_state.active_pool += exit_price * qty
|
||||
|
||||
if closed_trades:
|
||||
logger.info(
|
||||
"Backtest %s completed: %d trades, final value=$%.2f",
|
||||
backtest_id, len(closed_trades), portfolio_state.active_pool,
|
||||
)
|
||||
|
||||
# Compute final metrics
|
||||
metrics = self._perf.compute_metrics(
|
||||
closed_trades=closed_trades,
|
||||
|
||||
Reference in New Issue
Block a user