From 9b14233a247e10f95f4a8e95b76c5864894ba18f Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Wed, 23 Sep 2026 21:20:08 +0000 Subject: [PATCH] chore: remove code nothing uses - is_yahoo_safe, has_checkpoint, get_wall_times, the graph's curr_state and the project_dir config key - the CLI's final_report and current_agent state, its duplicate get_analysis_date and the save_report_to_disk wrapper - the dotenv import guard (a hard dependency) and a warning filter for langgraph-checkpoint 4.0.3 --- cli/main.py | 56 +----------------------- cli/utils.py | 33 -------------- tests/test_analyst_execution.py | 11 ++--- tests/test_checkpoint_resume.py | 19 ++++---- tests/test_symbol_utils.py | 12 ----- tradingagents/__init__.py | 44 ++++--------------- tradingagents/dataflows/symbol_utils.py | 7 --- tradingagents/default_config.py | 1 - tradingagents/graph/analyst_execution.py | 3 -- tradingagents/graph/checkpointer.py | 5 --- tradingagents/graph/trading_graph.py | 6 --- 11 files changed, 22 insertions(+), 175 deletions(-) diff --git a/cli/main.py b/cli/main.py index 5b5d20952..376017a63 100644 --- a/cli/main.py +++ b/cli/main.py @@ -112,9 +112,7 @@ class MessageBuffer: self.messages = deque(maxlen=max_length) self.tool_calls = deque(maxlen=max_length) self.current_report = None - self.final_report = None # Store the complete final report self.agent_status = {} - self.current_agent = None self.report_sections = {} self.selected_analysts = [] self._processed_message_ids = set() @@ -148,8 +146,6 @@ class MessageBuffer: # Reset other state self.current_report = None - self.final_report = None - self.current_agent = None self.messages.clear() self.tool_calls.clear() self._processed_message_ids.clear() @@ -186,7 +182,6 @@ class MessageBuffer: def update_agent_status(self, agent, status): if agent in self.agent_status: self.agent_status[agent] = status - self.current_agent = agent def update_report_section(self, section_name, content): if section_name in self.report_sections: @@ -219,50 +214,6 @@ class MessageBuffer: f"### {section_titles[latest_section]}\n{latest_content}" ) - # Update the final complete report - self._update_final_report() - - def _update_final_report(self): - report_parts = [] - - # Analyst Team Reports - use .get() to handle missing sections - analyst_sections = ["market_report", "sentiment_report", "news_report", "fundamentals_report"] - if any(self.report_sections.get(section) for section in analyst_sections): - report_parts.append("## Analyst Team Reports") - if self.report_sections.get("market_report"): - report_parts.append( - f"### Market Analysis\n{self.report_sections['market_report']}" - ) - if self.report_sections.get("sentiment_report"): - report_parts.append( - f"### Social Sentiment\n{self.report_sections['sentiment_report']}" - ) - if self.report_sections.get("news_report"): - report_parts.append( - f"### News Analysis\n{self.report_sections['news_report']}" - ) - if self.report_sections.get("fundamentals_report"): - report_parts.append( - f"### Fundamentals Analysis\n{self.report_sections['fundamentals_report']}" - ) - - # Research Team Reports - if self.report_sections.get("investment_plan"): - report_parts.append("## Research Team Decision") - report_parts.append(f"{self.report_sections['investment_plan']}") - - # Trading Team Reports - if self.report_sections.get("trader_investment_plan"): - report_parts.append("## Trading Team Plan") - report_parts.append(f"{self.report_sections['trader_investment_plan']}") - - # Portfolio Management Decision - if self.report_sections.get("final_trade_decision"): - report_parts.append("## Portfolio Management Decision") - report_parts.append(f"{self.report_sections['final_trade_decision']}") - - self.final_report = "\n\n".join(report_parts) if report_parts else None - message_buffer = MessageBuffer() @@ -780,11 +731,6 @@ def get_analysis_date(): ) -def save_report_to_disk(final_state, ticker: str, save_path: Path): - """Save the complete analysis report to disk (shared CLI/API writer).""" - return write_report_tree(final_state, ticker, save_path) - - def display_complete_report(final_state): """Display the complete analysis report sequentially (avoids truncation).""" console.print() @@ -1344,7 +1290,7 @@ def run_analysis(checkpoint: bool | None = None, portfolio=None): ).strip() save_path = Path(save_path_str) try: - report_file = save_report_to_disk(final_state, selections["ticker"], save_path) + report_file = write_report_tree(final_state, selections["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: diff --git a/cli/utils.py b/cli/utils.py index d3cca0cdf..1559379f0 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -99,39 +99,6 @@ def filter_analysts_for_asset_type( ] -def get_analysis_date() -> str: - """Prompt the user to enter a date in YYYY-MM-DD format.""" - import re - from datetime import datetime - - def validate_date(date_str: str) -> bool: - if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str): - return False - try: - datetime.strptime(date_str, "%Y-%m-%d") - return True - except ValueError: - return False - - date = questionary.text( - "Enter the analysis date (YYYY-MM-DD):", - validate=lambda x: validate_date(x.strip()) - or "Please enter a valid date in YYYY-MM-DD format.", - style=questionary.Style( - [ - ("text", "fg:green"), - ("highlighted", "noinherit"), - ] - ), - ).ask() - - if not date: - console.print("\n[red]No date provided. Exiting...[/red]") - exit(1) - - return date.strip() - - def _matching_choice(options, default): """The option value equal to ``default``, or None to leave the menu as is.""" return next((value for _, value in options if value == default), None) diff --git a/tests/test_analyst_execution.py b/tests/test_analyst_execution.py index 807caf6d4..4b2e67bc8 100644 --- a/tests/test_analyst_execution.py +++ b/tests/test_analyst_execution.py @@ -49,7 +49,7 @@ class AnalystWallTimeTrackerTests(unittest.TestCase): tracker.mark_started("market", started_at=10.0) tracker.mark_completed("market", completed_at=13.5) - self.assertEqual(tracker.get_wall_times(), {"market": 3.5}) + self.assertEqual(tracker.format_summary(), "Analyst wall time: Market 3.50s") def test_formats_summary_in_plan_order(self): plan = build_analyst_execution_plan(["news", "market"]) @@ -70,21 +70,18 @@ class AnalystWallTimeTrackerTests(unittest.TestCase): tracker = AnalystWallTimeTracker(plan) sync_analyst_tracker_from_chunk(tracker, {}, now=10.0) - self.assertEqual(tracker.get_wall_times(), {}) + self.assertEqual(tracker.format_summary(), "Analyst wall time: pending") sync_analyst_tracker_from_chunk( tracker, {"market_report": "done"}, now=13.0, ) - self.assertEqual(tracker.get_wall_times(), {"market": 3.0}) + self.assertEqual(tracker.format_summary(), "Analyst wall time: Market 3.00s") sync_analyst_tracker_from_chunk( tracker, {"market_report": "done", "news_report": "done"}, now=18.0, ) - self.assertEqual( - tracker.get_wall_times(), - {"market": 3.0, "news": 5.0}, - ) + self.assertEqual(tracker.format_summary(), "Analyst wall time: Market 3.00s | News 5.00s") diff --git a/tests/test_checkpoint_resume.py b/tests/test_checkpoint_resume.py index 6c5be500c..bc0037671 100644 --- a/tests/test_checkpoint_resume.py +++ b/tests/test_checkpoint_resume.py @@ -10,7 +10,6 @@ from tradingagents.graph.checkpointer import ( checkpoint_step, clear_checkpoint, get_checkpointer, - has_checkpoint, thread_id, ) @@ -63,7 +62,7 @@ class TestCheckpointResume(unittest.TestCase): graph.invoke({"count": 0}, config=cfg) # Checkpoint should exist at step 1 (analyst completed) - self.assertTrue(has_checkpoint(self.tmpdir, self.ticker, self.date)) + self.assertIsNotNone(checkpoint_step(self.tmpdir, self.ticker, self.date)) step = checkpoint_step(self.tmpdir, self.ticker, self.date) self.assertEqual(step, 1) @@ -90,11 +89,11 @@ class TestCheckpointResume(unittest.TestCase): with self.assertRaises(RuntimeError): graph.invoke({"count": 0}, config=cfg) - self.assertTrue(has_checkpoint(self.tmpdir, self.ticker, self.date)) + self.assertIsNotNone(checkpoint_step(self.tmpdir, self.ticker, self.date)) # Clear it clear_checkpoint(self.tmpdir, self.ticker, self.date) - self.assertFalse(has_checkpoint(self.tmpdir, self.ticker, self.date)) + self.assertIsNone(checkpoint_step(self.tmpdir, self.ticker, self.date)) # Fresh run succeeds from scratch _should_crash = False @@ -119,10 +118,10 @@ class TestCheckpointResume(unittest.TestCase): with self.assertRaises(RuntimeError): graph.invoke({"count": 0}, config={"configurable": {"thread_id": tid1}}) - self.assertTrue(has_checkpoint(self.tmpdir, self.ticker, self.date)) + self.assertIsNotNone(checkpoint_step(self.tmpdir, self.ticker, self.date)) # date2 should have no checkpoint - self.assertFalse(has_checkpoint(self.tmpdir, self.ticker, date2)) + self.assertIsNone(checkpoint_step(self.tmpdir, self.ticker, date2)) # Run with date2 — should start fresh and succeed _should_crash = False @@ -137,7 +136,7 @@ class TestCheckpointResume(unittest.TestCase): self.assertEqual(result["count"], 11) # Original date checkpoint still exists (untouched) - self.assertTrue(has_checkpoint(self.tmpdir, self.ticker, self.date)) + self.assertIsNotNone(checkpoint_step(self.tmpdir, self.ticker, self.date)) class TestCheckpointSignature(unittest.TestCase): @@ -178,9 +177,9 @@ class TestCheckpointSignature(unittest.TestCase): with self.assertRaises(RuntimeError): graph.invoke({"count": 0}, config={"configurable": {"thread_id": tid1}}) - self.assertTrue(has_checkpoint(self.tmpdir, self.ticker, self.date, sig1)) + self.assertIsNotNone(checkpoint_step(self.tmpdir, self.ticker, self.date, sig1)) # A different graph shape has no checkpoint to resume from. - self.assertFalse(has_checkpoint(self.tmpdir, self.ticker, self.date, sig2)) + self.assertIsNone(checkpoint_step(self.tmpdir, self.ticker, self.date, sig2)) _should_crash = False tid2 = thread_id(self.ticker, self.date, sig2) @@ -190,7 +189,7 @@ class TestCheckpointSignature(unittest.TestCase): result = graph.invoke({"count": 0}, config={"configurable": {"thread_id": tid2}}) self.assertEqual(result["count"], 11) # sig1's checkpoint remains untouched. - self.assertTrue(has_checkpoint(self.tmpdir, self.ticker, self.date, sig1)) + self.assertIsNotNone(checkpoint_step(self.tmpdir, self.ticker, self.date, sig1)) def test_run_signature_captures_graph_shape(self): from tradingagents.graph.trading_graph import TradingAgentsGraph diff --git a/tests/test_symbol_utils.py b/tests/test_symbol_utils.py index 8ddf877c8..2a6b9cb31 100644 --- a/tests/test_symbol_utils.py +++ b/tests/test_symbol_utils.py @@ -7,7 +7,6 @@ import pytest from tradingagents.dataflows.symbol_utils import ( NoMarketDataError, crypto_base, - is_yahoo_safe, normalize_symbol, ) @@ -88,17 +87,6 @@ class TestNoMarketDataError(unittest.TestCase): self.assertEqual(err.canonical, "FOOBAR") -@pytest.mark.unit -class TestIsYahooSafe(unittest.TestCase): - def test_accepts_structural_chars(self): - for sym in ("AAPL", "GC=F", "^GSPC", "BRK.B", "BTC-USD"): - self.assertTrue(is_yahoo_safe(sym)) - - def test_rejects_slash_and_space(self): - for sym in ("a/b", "AA PL", ""): - self.assertFalse(is_yahoo_safe(sym)) - - @pytest.mark.unit class TestCryptoBase(unittest.TestCase): def test_resolves_known_crypto_forms(self): diff --git a/tradingagents/__init__.py b/tradingagents/__init__.py index 87e37b3cc..6151e5e91 100644 --- a/tradingagents/__init__.py +++ b/tradingagents/__init__.py @@ -1,37 +1,9 @@ -import contextlib -import warnings +from dotenv import find_dotenv, load_dotenv -# Load .env files at package import so DEFAULT_CONFIG's env-var overlay -# (and every llm_clients consumer) sees the user's keys regardless of -# which entry point started the process. find_dotenv(usecwd=True) walks -# from the CWD, so the installed `tradingagents` console script picks up -# the project's .env instead of stepping up from site-packages. -# load_dotenv defaults to override=False, so it never clobbers values -# the caller has already exported. -try: - from dotenv import find_dotenv, load_dotenv - - load_dotenv(find_dotenv(usecwd=True)) - load_dotenv(find_dotenv(".env.enterprise", usecwd=True), override=False) -except ImportError: - pass - -# langchain-core 1.3.3 calls surface_langchain_deprecation_warnings() in -# its own __init__, which prepends default-action filters for its -# subclassed warning categories. To suppress a specific warning we must -# install our filter AFTER langchain-core has installed its own, so import -# it first. The package is a guaranteed transitive dep via langgraph. -with contextlib.suppress(ImportError): - import langchain_core # noqa: F401 - -# langgraph-checkpoint 4.0.3 calls Reviver() at module load without an -# explicit allowed_objects, which triggers a noisy pending-deprecation -# warning from langchain-core 1.3.3 on every interpreter start. The fix -# is already merged upstream (langchain-ai/langgraph#7743, 2026-05-08) -# and will arrive in the next langgraph-checkpoint release. Remove this -# block (and the langchain_core preload above) when we bump past it. -warnings.filterwarnings( - "ignore", - message=r"The default value of `allowed_objects`.*", - category=PendingDeprecationWarning, -) +# Load .env at package import so DEFAULT_CONFIG's env-var overlay and every LLM +# client see the user's keys whichever entry point started the process. +# usecwd=True walks from the working directory, so the installed console script +# finds the project's .env rather than looking beside site-packages. Values the +# caller has already exported are never overridden. +load_dotenv(find_dotenv(usecwd=True)) +load_dotenv(find_dotenv(".env.enterprise", usecwd=True), override=False) diff --git a/tradingagents/dataflows/symbol_utils.py b/tradingagents/dataflows/symbol_utils.py index 647f50444..bacababc0 100644 --- a/tradingagents/dataflows/symbol_utils.py +++ b/tradingagents/dataflows/symbol_utils.py @@ -71,9 +71,6 @@ _ALIASES = { "FRA40": "^FCHI", "EU50": "^STOXX50E", "HK50": "^HSI", } -# Yahoo symbols may contain letters, digits, and these structural characters. -_YAHOO_SAFE = re.compile(r"^[A-Za-z0-9._\-\^=]+$") - # HKEX codes as Yahoo spells them: the number zero-padded to 4 digits (#957). _HK_CODE = re.compile(r"^(\d{1,5})\.HK$") _SHANGHAI_SH = re.compile(r"^(\d{6})\.SH$") @@ -150,7 +147,3 @@ def normalize_symbol(raw: str) -> str: logger.info("Resolved symbol %r to Yahoo symbol %r", raw, canonical) return canonical - -def is_yahoo_safe(symbol: str) -> bool: - """True when ``symbol`` only contains characters Yahoo symbols use.""" - return bool(symbol) and _YAHOO_SAFE.fullmatch(symbol) is not None diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 32d7d8c66..918053dd7 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -70,7 +70,6 @@ def _apply_env_overrides(config: dict) -> dict: DEFAULT_CONFIG = _apply_env_overrides({ - "project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")), "results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR") or os.path.join(_TRADINGAGENTS_HOME, "logs"), "data_cache_dir": os.getenv("TRADINGAGENTS_CACHE_DIR") or os.path.join(_TRADINGAGENTS_HOME, "cache"), "memory_log_path": os.getenv("TRADINGAGENTS_MEMORY_LOG_PATH") or os.path.join(_TRADINGAGENTS_HOME, "memory", "trading_memory.md"), diff --git a/tradingagents/graph/analyst_execution.py b/tradingagents/graph/analyst_execution.py index 0d40dbb54..b9b702160 100644 --- a/tradingagents/graph/analyst_execution.py +++ b/tradingagents/graph/analyst_execution.py @@ -103,9 +103,6 @@ class AnalystWallTimeTracker: finished_at = monotonic() if completed_at is None else completed_at self._wall_times[analyst_key] = max(0.0, finished_at - started_at) - def get_wall_times(self) -> dict[str, float]: - return dict(self._wall_times) - def format_summary(self) -> str: parts = [] for spec in self.plan.specs: diff --git a/tradingagents/graph/checkpointer.py b/tradingagents/graph/checkpointer.py index d40255fa4..abe5cea87 100644 --- a/tradingagents/graph/checkpointer.py +++ b/tradingagents/graph/checkpointer.py @@ -51,11 +51,6 @@ def get_checkpointer(data_dir: str | Path, ticker: str) -> Generator[SqliteSaver conn.close() -def has_checkpoint(data_dir: str | Path, ticker: str, date: str, signature: str = "") -> bool: - """Check whether a resumable checkpoint exists for ticker+date.""" - return checkpoint_step(data_dir, ticker, date, signature) is not None - - def checkpoint_step(data_dir: str | Path, ticker: str, date: str, signature: str = "") -> int | None: """Return the step number of the latest checkpoint, or None if none exists.""" db = _db_path(data_dir, ticker) diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index d8b63c619..64c7099af 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -145,9 +145,6 @@ class TradingAgentsGraph: ) self.reflector = Reflector(self.quick_thinking_llm) - # State tracking - self.curr_state = None - # Graph-shape-affecting run choices, kept for the checkpoint signature. self.selected_analysts = tuple(selected_analysts) @@ -538,9 +535,6 @@ class TradingAgentsGraph: else: final_state = self.graph.invoke(graph_input, **args) - # Store current state for reflection. - self.curr_state = final_state - # Log state to disk. self._log_state(trade_date, final_state)