diff --git a/cli/main.py b/cli/main.py index 264e363b5..7fbb53b15 100644 --- a/cli/main.py +++ b/cli/main.py @@ -3,6 +3,8 @@ import sys import typer from cli.display import console +from cli.models import AnalystType, AssetType +from cli.prompts import filter_analysts_for_asset_type, parse_analysts from cli.run import run_analysis from tradingagents.backtest import iter_grid, run_backtest, summarize from tradingagents.default_config import DEFAULT_CONFIG @@ -101,7 +103,7 @@ def backtest( end: str = typer.Option(..., "--end", help="Last analysis date, YYYY-MM-DD"), every: int = typer.Option(7, "--every", help="Days between analysis dates"), analysts: str = typer.Option( - None, "--analysts", help="Comma-separated analysts to run; omit for all four" + None, "--analysts", help="Comma-separated analysts to run: market, sentiment, news, fundamentals; omit for all the asset type allows" ), asset_type: str = typer.Option("stock", "--asset-type", help="stock or crypto"), portfolio: str = typer.Option( @@ -116,6 +118,11 @@ def backtest( try: dates = iter_grid(start, end, every) book = load_portfolio(portfolio) if portfolio else None + kind = AssetType(asset_type.strip().lower()) + # The analysts are named and checked as for an analysis; without a + # choice, every analyst the asset type allows runs. + chosen = (parse_analysts(analysts, kind) if analysts + else filter_analysts_for_asset_type(list(AnalystType), kind)) except ValueError as exc: console.print(f"[red]{exc}[/red]") raise typer.Exit(code=1) from None @@ -128,9 +135,8 @@ def backtest( def show_progress(done, total, ticker, date): console.print(f"[dim][{done}/{total}] {ticker} {date}[/dim]") - kwargs = {"asset_type": asset_type, "portfolio": book, "run_id": run_id, "progress": show_progress} - if analysts: - kwargs["selected_analysts"] = [a.strip().lower() for a in analysts.split(",") if a.strip()] + kwargs = {"asset_type": kind.value, "portfolio": book, "run_id": run_id, "progress": show_progress, + "selected_analysts": [a.value for a in chosen]} try: result = run_backtest(names, dates, DEFAULT_CONFIG, **kwargs) diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py index 845b817fb..a0bd36a9a 100644 --- a/tests/test_cli_commands.py +++ b/tests/test_cli_commands.py @@ -151,3 +151,32 @@ def test_backtest_shows_each_cell_and_how_to_continue(runner, monkeypatch, tmp_p assert "[1/2] NVDA 2026-06-01" in result.output and "[2/2] NVDA 2026-06-08" in result.output assert f"--run-id {_Result(tmp_path).run_id}" in result.output + + +@pytest.mark.unit +@pytest.mark.parametrize("args, analysts", [ + pytest.param(["--analysts", "sentiment,market"], ["market", "social"], id="named_as_in_analyze"), + pytest.param(["--asset-type", "crypto"], ["market", "social", "news"], id="crypto_default"), +]) +def test_backtest_takes_its_analysts_as_an_analysis_does(runner, monkeypatch, tmp_path, args, analysts): + swept = [] + monkeypatch.setattr(m, "run_backtest", lambda *a, **kw: swept.append(kw) or _Result(tmp_path)) + monkeypatch.setattr(m, "summarize", lambda log: _Summary()) + + result = runner.invoke(m.app, ["backtest", "NVDA", "--start", "2026-06-01", "--end", "2026-06-01", *args]) + + assert result.exit_code == 0, result.output + assert swept[0]["selected_analysts"] == analysts + + +@pytest.mark.unit +@pytest.mark.parametrize("args", [ + pytest.param(["--analysts", "fundamentals", "--asset-type", "crypto"], id="unavailable_for_crypto"), + pytest.param(["--asset-type", "bond"], id="unknown_asset_type"), +]) +def test_backtest_refuses_a_selection_before_the_sweep(runner, monkeypatch, args): + monkeypatch.setattr(m, "run_backtest", lambda *a, **kw: pytest.fail("the sweep started")) + + result = runner.invoke(m.app, ["backtest", "NVDA", "--start", "2026-06-01", "--end", "2026-06-01", *args]) + + assert result.exit_code == 1