"""B3b: the `backtest-equity` CLI entrypoint runs the walk-forward equity backtest against the DEDICATED backtest warehouse (settings.backtest_warehouse_path), evaluates the 3 price-only constructions through the gauntlet, and writes a JSON report. No network: a synthetic warehouse is built and the command is pointed at it via FXHNT_BACKTEST_WAREHOUSE_PATH.""" from __future__ import annotations import datetime as dt import json from typer.testing import CliRunner from fxhnt.adapters.warehouse.duckdb_feature_store import DuckDbFeatureStore _SPD = 86_400 def _build_warehouse(path: str) -> DuckDbFeatureStore: """3 names over ~2 years of daily bars (mirrors the runner integration test): AAA strong uptrend, BBB mild, CCC downtrend — a non-degenerate cross-section for long/ls/tilt.""" store = DuckDbFeatureStore(path) start = dt.date(2022, 1, 3) n = 520 # ~2 trading years drifts = {"AAA": 0.0015, "BBB": 0.0003, "CCC": -0.0010} items = [] members = [] for sym, dr in drifts.items(): rows = [] px = 100.0 for i in range(n): day = start + dt.timedelta(days=i) px *= (1.0 + dr) ts = (day - dt.date(1970, 1, 1)).days * _SPD rows.append((ts, {"adjclose": px, "close": px, "volume": 1_000_000.0})) items.append((sym, rows)) members.append((sym, "NYSE", "2000-01-01", "2026-06-01")) store.write_features_bulk(items) store.upsert_membership(members) return store def test_cli_backtest_equity_writes_report(tmp_path, monkeypatch) -> None: import fxhnt.cli as cli wh = str(tmp_path / "wh.duckdb") _build_warehouse(wh).close() out = tmp_path / "report.json" monkeypatch.setenv("FXHNT_BACKTEST_WAREHOUSE_PATH", wh) cli.get_settings.cache_clear() # settings are lru_cached — pick up the env override res = CliRunner().invoke(cli.app, ["backtest-equity", "--n", "10", "--out", str(out)]) cli.get_settings.cache_clear() assert res.exit_code == 0, res.output assert out.exists(), "backtest-equity must write the report JSON at --out" report = json.loads(out.read_text()) assert set(report["constructions"]) == {"long", "ls", "tilt"} for block in report["constructions"].values(): assert {"stats", "verdict"} <= set(block) assert {"passed", "dsr", "is_sharpe", "oos_sharpe"} <= set(block["verdict"]) def test_cli_backtest_equity_bounded_candidate_flags(tmp_path, monkeypatch) -> None: """The bounded memory pre-filter flags (--candidate-top-k / --candidate-min-history) are exposed and passed to the runner; with thresholds the 3 synthetic names qualify -> full report.""" import fxhnt.cli as cli wh = str(tmp_path / "wh.duckdb") _build_warehouse(wh).close() out = tmp_path / "report.json" monkeypatch.setenv("FXHNT_BACKTEST_WAREHOUSE_PATH", wh) cli.get_settings.cache_clear() # settings are lru_cached — pick up the env override res = CliRunner().invoke( cli.app, [ "backtest-equity", "--n", "10", "--out", str(out), "--candidate-top-k", "100", "--candidate-min-history", "10", ], ) cli.get_settings.cache_clear() assert res.exit_code == 0, res.output assert out.exists(), "backtest-equity must write the report JSON at --out" report = json.loads(out.read_text()) assert set(report["constructions"]) == {"long", "ls", "tilt"}