import sys import typer from cli.display import console from cli.run import run_analysis from tradingagents.backtest import iter_grid, run_backtest, summarize from tradingagents.default_config import DEFAULT_CONFIG from tradingagents.portfolio import load_portfolio # prompt_toolkit's win32 output module is importable only on Windows (it asserts # the platform at import time), so gate on the platform rather than catching the # failure — that way a genuinely broken prompt_toolkit on Windows still surfaces # instead of silently disabling the handler below. Off Windows this stays an # empty tuple, which `except` accepts and never matches (#1138). if sys.platform == "win32": # pragma: no cover - platform dependent from prompt_toolkit.output.win32 import NoConsoleScreenBufferError _NO_CONSOLE_ERRORS: tuple[type[BaseException], ...] = (NoConsoleScreenBufferError,) else: _NO_CONSOLE_ERRORS = () app = typer.Typer( name="TradingAgents", help="TradingAgents CLI: Multi-Agents LLM Financial Trading Framework", add_completion=True, # Enable shell completion ) @app.callback(invoke_without_command=True) def analyze( ctx: typer.Context, checkpoint: bool | None = typer.Option( None, "--checkpoint/--no-checkpoint", help="Enable/disable checkpoint-resume (save state after each node so a " "crashed run can resume). Omit to honor TRADINGAGENTS_CHECKPOINT_ENABLED.", ), clear_checkpoints: bool = typer.Option( False, "--clear-checkpoints", help="Delete all saved checkpoints before running (force fresh start).", ), portfolio: str = typer.Option( None, "--portfolio", help="JSON file with current holdings and cash, so the trader, risk and " "portfolio agents size against your actual position.", ), ): """Run an analysis. This is what a bare `tradingagents` does.""" if ctx.invoked_subcommand is not None: return if clear_checkpoints: from tradingagents.graph.checkpointer import clear_all_checkpoints n = clear_all_checkpoints(DEFAULT_CONFIG["data_cache_dir"]) console.print(f"[yellow]Cleared {n} checkpoint(s).[/yellow]") portfolio_context = None if portfolio: try: portfolio_context = load_portfolio(portfolio) except ValueError as exc: console.print(f"[red]{exc}[/red]") raise typer.Exit(code=1) from None try: run_analysis(checkpoint=checkpoint, portfolio=portfolio_context) except _NO_CONSOLE_ERRORS: # A terminal with no console buffer cannot host the interactive prompts. # Emit one actionable line on stderr instead of a prompt_toolkit # traceback; plain text, since rich may not render here either (#1138). typer.echo( "Error: no Windows console available. The interactive CLI needs a real " "console buffer — run it from Windows Terminal, PowerShell, or cmd.exe " "rather than a piped or embedded terminal.", err=True, ) raise typer.Exit(code=1) from None @app.command() def backtest( tickers: str = typer.Argument(..., help="Comma-separated tickers, e.g. NVDA,AAPL"), start: str = typer.Option(..., "--start", help="First analysis date, YYYY-MM-DD"), 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" ), asset_type: str = typer.Option("stock", "--asset-type", help="stock or crypto"), portfolio: str = typer.Option( None, "--portfolio", help="JSON file with holdings and cash, held constant across the grid" ), run_id: str = typer.Option( None, "--run-id", help="Continue an earlier sweep: its cells are skipped and its log reused" ), ): """Score past decisions over a grid of tickers and dates.""" try: dates = iter_grid(start, end, every) book = load_portfolio(portfolio) if portfolio else None except ValueError as exc: console.print(f"[red]{exc}[/red]") raise typer.Exit(code=1) from None names = [t.strip() for t in tickers.split(",") if t.strip()] if not names: console.print("[red]No ticker to analyze; pass them comma-separated, e.g. NVDA,AAPL[/red]") raise typer.Exit(code=1) kwargs = {"asset_type": asset_type, "portfolio": book, "run_id": run_id} if analysts: kwargs["selected_analysts"] = [a.strip().lower() for a in analysts.split(",") if a.strip()] try: result = run_backtest(names, dates, DEFAULT_CONFIG, **kwargs) except Exception as exc: # a missing key or an unknown analyst is a setup error console.print(f"[red]{exc}[/red]") raise typer.Exit(code=1) from None console.print(summarize(result).render()) console.print(f"\nRan {result.cells_run} cells, skipped {result.skipped}. Log: {result.log_path}") for ticker, date, reason in result.failures: console.print(f"[yellow]failed:[/yellow] {ticker} {date}: {reason}") for ticker, reason in result.settlement_failures: console.print(f"[yellow]unsettled:[/yellow] {ticker}: {reason}") if __name__ == "__main__": app()