refactor(cli): save a run's reports through the graph, as a script does

- TradingAgentsGraph.default_report_path names the results_dir/reports/TICKER_stamp folder; save_reports and the CLI both use it
This commit is contained in:
Yijia-Xiao
2026-09-29 05:57:18 +00:00
parent ad39133569
commit e1a19fd78e
4 changed files with 20 additions and 19 deletions
+4 -9
View File
@@ -1,6 +1,5 @@
"""Running one analysis from the CLI: build the graph, stream it into the live view, save the report."""
import datetime
import os
import sys
import time
@@ -31,7 +30,6 @@ from tradingagents.graph.analyst_execution import (
build_analyst_execution_plan,
)
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.reporting import write_report_tree
def _run_directory(config: dict, ticker: str, trade_date: str) -> Path:
@@ -384,29 +382,26 @@ def run_analysis(checkpoint: bool | None = None, portfolio=None, flags=None):
)
console.print(f"[dim]{analyst_wall_time_tracker.format_summary()}[/dim]")
_offer_reports(final_state, graph, config, selections["ticker"],
_offer_reports(final_state, graph, selections["ticker"],
save=flags.get("save"), show=flags.get("show"))
def _offer_reports(final_state, graph, config, ticker, save=None, show=None):
def _offer_reports(final_state, graph, ticker, save=None, show=None):
"""Save the report tree and show it; ``save``/``show`` answer the questions when given."""
asked = save is None
if asked:
save = typer.prompt("Save report?", default="Y").strip().upper() in ("Y", "YES", "")
if save:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# Under results_dir, not the working directory: in Docker the working
# directory is inside the container and the report goes with it, while
# results_dir is the mounted volume the rest of the run already writes to.
save_path = (Path(config["results_dir"]) / "reports"
/ f"{safe_ticker_component(ticker)}_{timestamp}")
save_path = graph.default_report_path(ticker)
if asked: # someone at the prompt may pick another folder
save_path = Path(typer.prompt(
"Save path (press Enter for default)", default=str(save_path)
).strip())
try:
report_file = write_report_tree(final_state, ticker, save_path,
settings=graph.run_settings())
report_file = graph.save_reports(final_state, ticker, save_path)
console.print(f"\n[green]✓ Report saved to:[/green] {save_path.resolve()}")
console.print(f" [dim]Complete report:[/dim] {report_file.name}")
except Exception as e:
+6 -2
View File
@@ -95,10 +95,14 @@ def test_save_and_show_answer_the_questions_after_the_run(monkeypatch, tmp_path)
monkeypatch.setattr(run.typer, "prompt", no_prompt)
shown = []
monkeypatch.setattr(run, "display_complete_report", lambda state: shown.append(state))
graph = type("G", (), {"run_settings": lambda self: {}})()
from tradingagents.graph.trading_graph import TradingAgentsGraph
graph = object.__new__(TradingAgentsGraph)
graph.config = {"results_dir": str(tmp_path)}
graph.run_settings = lambda: {}
state = {"market_report": "M", "final_trade_decision": "**Rating**: Hold"}
run._offer_reports(state, graph, {"results_dir": str(tmp_path)}, "NVDA", save=True, show=False)
run._offer_reports(state, graph, "NVDA", save=True, show=False)
assert list(tmp_path.glob("reports/NVDA_*/complete_report.md"))
assert shown == []
+4 -2
View File
@@ -50,8 +50,10 @@ def test_save_reports_explicit_path(tmp_path):
@pytest.mark.unit
def test_save_reports_defaults_under_results_dir(tmp_path):
mock_self = SimpleNamespace(config={"results_dir": str(tmp_path)}, run_settings=lambda: SETTINGS)
out = TradingAgentsGraph.save_reports(mock_self, _state(), "AAPL")
graph = object.__new__(TradingAgentsGraph)
graph.config = {"results_dir": str(tmp_path)}
graph.run_settings = lambda: SETTINGS
out = graph.save_reports(_state(), "AAPL")
assert out.exists()
assert out.parent.parent.name == "reports" # results_dir/reports/AAPL_<stamp>/...
assert out.parent.name.startswith("AAPL_")
+6 -6
View File
@@ -294,14 +294,14 @@ class TradingAgentsGraph:
an explicit ``save_path`` or let it default under ``results_dir``.
"""
if save_path is None:
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = (
Path(self.config["results_dir"])
/ "reports"
/ f"{safe_ticker_component(ticker)}_{stamp}"
)
save_path = self.default_report_path(ticker)
return write_report_tree(final_state, ticker, save_path, settings=self.run_settings())
def default_report_path(self, ticker) -> Path:
"""Where a run's reports go unless told otherwise: under results_dir, stamped now."""
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
return Path(self.config["results_dir"]) / "reports" / f"{safe_ticker_component(ticker)}_{stamp}"
def create_run_state(self, company_name, trade_date, asset_type: str = "stock", portfolio=None):
"""Build a run's initial state; propagate() and the CLI both start here.