"""run_backtest PIT 유니버스 순수 함수 회귀 (네트워크·DB 미사용).""" import sys import types from datetime import date, timedelta import pytest if "FinanceDataReader" not in sys.modules: try: import FinanceDataReader # noqa: F401 except ImportError: sys.modules["FinanceDataReader"] = types.ModuleType("FinanceDataReader") import run_backtest as rb @pytest.fixture(autouse=True) def _no_fdr(monkeypatch): def boom(*a, **k): raise AssertionError("FDR 실호출 금지") if hasattr(rb.fdr, "DataReader") or True: monkeypatch.setattr(rb.fdr, "DataReader", boom, raising=False) def _bar(code, d, c): return rb.BarData(d, c, c, c, c, 1000, code, code) def test_rank_includes_delisted_and_excludes_short_history(): n = rb.PIT_MIN_HISTORY_BARS rows = [("A", "a", 5.0, n), ("DEAD", "d", 9.0, n), ("NEW", "n", 99.0, n - 1), ("Z", "z", 0, n)] assert [c for c, _ in rb.rank_pit_universe_v2(rows, 10)] == ["DEAD", "A"] assert [c for c, _ in rb.rank_pit_universe_v2(rows, 1)] == ["DEAD"] def test_delisted_gets_writeoff_bar_default_zero_recovery(): end = date(2026, 3, 25) last = end - timedelta(days=rb.PIT_DELIST_GRACE_DAYS + 40) bars = {"DEAD": [_bar("DEAD", last, 5000)], "LIVE": [_bar("LIVE", end, 7000)]} out = rb.apply_delisting_writeoff_v2(bars, end) assert out == ["DEAD"] assert len(bars["LIVE"]) == 1 term = bars["DEAD"][-1] assert term.trade_date == last + timedelta(days=1) assert term.close_price == 1.0 and term.volume == 0 def test_recovery_ratio_and_clamp(): end = date(2026, 3, 25) last = end - timedelta(days=200) bars = {"D": [_bar("D", last, 5000)]} rb.apply_delisting_writeoff_v2(bars, end, recovery=0.5) assert bars["D"][-1].close_price == 2500.0 bars = {"D": [_bar("D", last, 5000)]} rb.apply_delisting_writeoff_v2(bars, end, recovery=7) # 상한 1.0 클램프 assert bars["D"][-1].close_price == 5000.0 def test_survivor_within_grace_not_delisted(): end = date(2026, 3, 25) bars = {"X": [_bar("X", end - timedelta(days=rb.PIT_DELIST_GRACE_DAYS), 100)]} assert rb.apply_delisting_writeoff_v2(bars, end) == [] def test_db_url_requires_env(monkeypatch): monkeypatch.delenv("DATABASE_URL", raising=False) with pytest.raises(RuntimeError): rb._db_url_for_asyncpg_v2() monkeypatch.setenv("DATABASE_URL", "postgresql+asyncpg://u:p@h/db_test") assert rb._db_url_for_asyncpg_v2().startswith("postgresql://")