From 9068f73b51fbedb23439c2b2d16c81bd496f9433 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Thu, 18 Jun 2026 09:09:01 +0200 Subject: [PATCH] feat(b3b): expose candidate-top-k/min-history on backtest-equity CLI (use bounded path by default) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/fxhnt/cli.py | 3 +++ tests/integration/test_backtest_equity_cli.py | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/fxhnt/cli.py b/src/fxhnt/cli.py index 0f1a8ff..1f2406b 100644 --- a/src/fxhnt/cli.py +++ b/src/fxhnt/cli.py @@ -514,6 +514,8 @@ def backtest_equity( cost_bps: float = typer.Option(15.0, "--cost-bps", help="Cost in bps per unit (two-way) turnover."), borrow_annual: float = typer.Option(0.0, "--borrow-annual", help="Annual borrow cost on short notional."), out: str = typer.Option("/backtest-data/equity_backtest_report.json", "--out", help="Report JSON path."), + candidate_top_k: int = typer.Option(2000, help="Bounded candidate universe: top-K most-liquid names (caps memory; the full universe OOMs)."), + candidate_min_history: int = typer.Option(300, help="Min daily bars (close+volume) a name needs to be a candidate."), ) -> None: """B3b: walk-forward backtest of the 3 price-only equity-factor constructions against the DEDICATED survivorship-free backtest warehouse (settings.backtest_warehouse_path) -> gauntlet verdict + JSON report.""" @@ -527,6 +529,7 @@ def backtest_equity( try: result = EquityBacktestRunner( store, n=n, cost_bps_per_turnover=cost_bps, borrow_annual=borrow_annual, + candidate_top_k=candidate_top_k, candidate_min_history=candidate_min_history, ).run() report = evaluate_constructions( result, diff --git a/tests/integration/test_backtest_equity_cli.py b/tests/integration/test_backtest_equity_cli.py index 9750153..8b471f9 100644 --- a/tests/integration/test_backtest_equity_cli.py +++ b/tests/integration/test_backtest_equity_cli.py @@ -57,3 +57,29 @@ def test_cli_backtest_equity_writes_report(tmp_path, monkeypatch) -> None: 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"}