"""全量模拟 (full mode) 尾部执行回归测试。""" from __future__ import annotations from datetime import date, timedelta import polars as pl from app.backtest.engine import BacktestEngine, MatcherConfig def _panel_with_tail(symbols: list[str], n_data_days: int) -> pl.DataFrame: start = date(2024, 1, 1) rows = [] for sym in symbols: for i in range(n_data_days): px = 10.0 + i rows.append({ "symbol": sym, "date": start + timedelta(days=i), "open": px, "high": px, "low": px, "close": px, "volume": 100_000, "signal_limit_up": False, "signal_limit_down": False, }) return pl.DataFrame(rows).sort(["symbol", "date"]) def test_full_simulation_executes_signal_at_tail(): """信号集中在正式区间最后一天时, tail 数据应允许次日开盘买入并按策略退出。""" n_days = 6 panel = _panel_with_tail(["A"], n_days + 3) start = date(2024, 1, 1) end = start + timedelta(days=n_days - 1) entry_vals = [] for row in panel.select(["symbol", "date"]).iter_rows(named=True): entry_vals.append(row["date"] == end) entry_mask = pl.Series(entry_vals, dtype=pl.Boolean) exit_mask = pl.Series([False] * len(panel), dtype=pl.Boolean) result = BacktestEngine(repo=None).simulate_independent_candidates( # type: ignore[arg-type] panel, entry_mask, exit_mask, MatcherConfig(matching="open_t+1", fees_pct=0, slippage_bps=0, max_hold_days=2), ) assert not result.stats.get("error"), f"unexpected error: {result.stats.get('error')}" assert result.stats.get("full_kind") == "candidate_execution" assert result.stats.get("n_candidates") == 1 assert result.stats.get("n_trades") == 1 assert len(result.trades) == 1 trade = result.trades[0] assert trade.entry_signal_date == str(end) assert trade.entry_date == str(end + timedelta(days=1)) assert trade.exit_reason == "max_hold"