From 5ac5786d0d2a021bf915bff19cafb901ab994666 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Thu, 24 Sep 2026 18:51:45 +0000 Subject: [PATCH] docs: call it the memory log throughout - docstrings, comments, messages and the README section use "memory log", matching TradingMemoryLog and memory_log_path - the write, read and settle paths are named for what they do, not by design phase --- README.md | 8 ++++---- cli/run.py | 2 +- tests/test_backtest.py | 4 ++-- ...test_cli_decision_log.py => test_cli_memory_log.py} | 4 ++-- tradingagents/backtest.py | 8 ++++---- tradingagents/graph/trading_graph.py | 2 +- tradingagents/memory/log.py | 10 +++++----- tradingagents/memory/reflection.py | 8 +++++--- tradingagents/memory/settlement.py | 2 +- 9 files changed, 25 insertions(+), 23 deletions(-) rename tests/{test_cli_decision_log.py => test_cli_memory_log.py} (97%) diff --git a/README.md b/README.md index 82ca5e24d..2fd419e7d 100644 --- a/README.md +++ b/README.md @@ -304,9 +304,9 @@ An empty `positions` list means a flat book, which is different from passing not TradingAgents persists two kinds of state across runs. -### Decision log +### Memory log -The decision log is always on. Each completed run appends its decision to `~/.tradingagents/memory/trading_memory.md`. On the next run for the same ticker, TradingAgents fetches the realised return (raw, and alpha against the instrument's regional benchmark), generates a one-paragraph reflection, and injects the most recent same-ticker decisions plus recent cross-ticker lessons into the Portfolio Manager prompt, so each analysis carries forward what worked and what didn't. +The memory log is always on. Each completed run appends its decision to `~/.tradingagents/memory/trading_memory.md`. On the next run for the same ticker, TradingAgents fetches the realised return (raw, and alpha against the instrument's regional benchmark), generates a one-paragraph reflection, and injects the most recent same-ticker decisions plus recent cross-ticker lessons into the Portfolio Manager prompt, so each analysis carries forward what worked and what didn't. Override the path with `TRADINGAGENTS_MEMORY_LOG_PATH`. @@ -330,7 +330,7 @@ _, decision = ta.propagate("NVDA", "2026-09-01") ## Evaluating decisions over time -One run gives one decision, which cannot tell you whether the system decides well. `run_backtest` runs the same pipeline over a grid of tickers and dates, writes to a decision log of its own, and scores the decisions whose holding window has since traded. +One run gives one decision, which cannot tell you whether the system decides well. `run_backtest` runs the same pipeline over a grid of tickers and dates, writes to a memory log of its own, and scores the decisions whose holding window has since traded. ```python from tradingagents.backtest import iter_grid, run_backtest, summarize @@ -346,7 +346,7 @@ From the CLI: tradingagents backtest NVDA,AAPL --start 2026-06-01 --end 2026-08-01 --every 7 ``` -Each cell is scored on realized alpha against the instrument's regional benchmark, grouped by rating. Your own decision log is never written to, and re-running the same grid with `run_id=result.run_id` skips the cells that already ran, so an interrupted sweep continues where it stopped. +Each cell is scored on realized alpha against the instrument's regional benchmark, grouped by rating. Your own memory log is never written to, and re-running the same grid with `run_id=result.run_id` skips the cells that already ran, so an interrupted sweep continues where it stopped. ## Reproducibility diff --git a/cli/run.py b/cli/run.py index 7d0bda236..01f6a6acb 100644 --- a/cli/run.py +++ b/cli/run.py @@ -199,7 +199,7 @@ def run_analysis(checkpoint: bool | None = None, portfolio=None): ) update_display(layout, spinner_text, stats_handler=stats_handler, start_time=start_time) - # The same initial state propagate() builds: settled decision log, past + # The same initial state propagate() builds: settled memory log, past # context and resolved instrument identity. init_agent_state = graph.create_run_state( selections["ticker"], selections["analysis_date"], selections["asset_type"], portfolio diff --git a/tests/test_backtest.py b/tests/test_backtest.py index e9aab6c20..f029dee56 100644 --- a/tests/test_backtest.py +++ b/tests/test_backtest.py @@ -1,4 +1,4 @@ -"""Backtesting: many single-shot decisions, scored by the decision log. +"""Backtesting: many single-shot decisions, scored by the memory log. A run already records its rating and later settles it with realized and alpha return against the regional benchmark. A backtest is that machinery over a grid @@ -76,7 +76,7 @@ def _config(tmp_path): @pytest.mark.unit -def test_the_live_decision_log_is_never_written(tmp_path): +def test_the_live_memory_log_is_never_written(tmp_path): config = _config(tmp_path) result = run_backtest(["NVDA"], ["2026-01-05", "2026-01-12"], config) diff --git a/tests/test_cli_decision_log.py b/tests/test_cli_memory_log.py similarity index 97% rename from tests/test_cli_decision_log.py rename to tests/test_cli_memory_log.py index d6d01e784..9e266fbfb 100644 --- a/tests/test_cli_decision_log.py +++ b/tests/test_cli_memory_log.py @@ -1,4 +1,4 @@ -"""The CLI must use the decision log the same way propagate() does. +"""The CLI must use the memory log the same way propagate() does. The CLI streams the graph itself instead of calling propagate(), so memory steps that lived only in propagate() never ran on the primary entry point: pending @@ -160,7 +160,7 @@ def _run_cli(monkeypatch, tmp_path, fake): @pytest.mark.unit -def test_cli_run_uses_the_decision_log_like_propagate(tmp_path, monkeypatch): +def test_cli_run_uses_the_memory_log_like_propagate(tmp_path, monkeypatch): fake = _FakeGraph() _run_cli(monkeypatch, tmp_path, fake) diff --git a/tradingagents/backtest.py b/tradingagents/backtest.py index b249b4e96..49afb06d6 100644 --- a/tradingagents/backtest.py +++ b/tradingagents/backtest.py @@ -2,7 +2,7 @@ One run yields one decision, so it cannot say whether the system decides well. This runs the same machinery over many (ticker, date) cells and reads the -aggregate. The decision log is the results table: every run already records its +aggregate. The memory log is the results table: every run already records its rating and later settles it with realized and alpha return against the instrument's regional benchmark, so there is nothing to record separately. @@ -134,7 +134,7 @@ def run_backtest( selected_analysts=("market", "social", "news", "fundamentals"), run_id: str | None = None, ) -> BacktestResult: - """Analyze every ticker on every date, into a decision log of this run's own. + """Analyze every ticker on every date, into a memory log of this run's own. The live log stays untouched: a sweep would otherwise flood the context that real runs read back. Cells already in this run's log are skipped, so an @@ -176,13 +176,13 @@ def run_backtest( def summarize(source: BacktestResult | str | Path) -> BacktestSummary: - """Score the settled decisions of a backtest, or of a decision log at a path, by rating.""" + """Score the settled decisions of a backtest, or of a memory log at a path, by rating.""" if isinstance(source, BacktestResult): path = source.log_path # a run whose cells all failed wrote no log: nothing to score elif Path(source).is_file(): path = Path(source) else: - raise FileNotFoundError(f"no decision log at {source}") + raise FileNotFoundError(f"no memory log at {source}") entries = TradingMemoryLog({"memory_log_path": str(path)}).load_entries() # A decision with no readable rating has no direction, so it can neither # count for nor against the system; it is reported as unscored instead. diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 1795907db..cd0296429 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -262,7 +262,7 @@ class TradingAgentsGraph: Settles this ticker's pending decisions first, then injects the lessons known by the trade date for the Portfolio Manager (#1251) and the resolved instrument identity for every agent (#814). An entry point that - assembled the state itself would skip the decision log. + assembled the state itself would skip the memory log. """ self.settle_pending(company_name) return self.propagator.create_initial_state( diff --git a/tradingagents/memory/log.py b/tradingagents/memory/log.py index 1b5ab6039..e06a1afa7 100644 --- a/tradingagents/memory/log.py +++ b/tradingagents/memory/log.py @@ -1,4 +1,4 @@ -"""Append-only markdown decision log for TradingAgents.""" +"""The memory log: an append-only markdown record of each decision and, once settled, its outcome.""" import re from pathlib import Path @@ -25,7 +25,7 @@ class TradingMemoryLog: # Optional cap on resolved entries. None disables rotation. self._max_entries = cfg.get("memory_log_max_entries") - # --- Write path (Phase A) --- + # --- Write: a run records its decision --- def store_decision( self, @@ -56,7 +56,7 @@ class TradingMemoryLog: with open(self._log_path, "a", encoding="utf-8") as f: f.write(entry) - # --- Read path (Phase A) --- + # --- Read --- def load_entries(self) -> list[dict]: """Parse all entries from log. Returns list of dicts.""" @@ -72,7 +72,7 @@ class TradingMemoryLog: return entries def get_pending_entries(self) -> list[dict]: - """Return entries with outcome:pending (for Phase B).""" + """Return entries with outcome:pending, for settlement.""" return [e for e in self.load_entries() if e.get("pending")] def get_past_context( @@ -114,7 +114,7 @@ class TradingMemoryLog: parts.extend(self._format_reflection_only(e) for e in cross) return "\n\n".join(parts) - # --- Update path (Phase B) --- + # --- Settle: record a decision's outcome and reflection --- def update_with_outcome( self, diff --git a/tradingagents/memory/reflection.py b/tradingagents/memory/reflection.py index 198333ffe..ba8bf5f31 100644 --- a/tradingagents/memory/reflection.py +++ b/tradingagents/memory/reflection.py @@ -1,3 +1,5 @@ +"""Reflection: a settled decision's outcome turned into a short lesson for later runs.""" + from typing import Any @@ -9,7 +11,7 @@ class Reflector: self.quick_thinking_llm = quick_thinking_llm def _system_prompt(self, holding_days: int) -> str: - """Concise prompt for reflect_on_final_decision (Phase B log entries). + """Concise prompt for reflect_on_final_decision (settled memory log entries). Produces 2-4 sentences of plain prose, compact enough to be re-injected into future agent prompts without bloating the context window. The @@ -27,7 +29,7 @@ class Reflector: "and say so plainly if the window is too short to judge the thesis.\n" "2. Which part of the investment thesis this window supports or undercuts.\n" "3. One concrete lesson to apply to the next similar analysis.\n\n" - "Be specific and terse. Your output will be stored verbatim in a decision log " + "Be specific and terse. Your output will be stored verbatim in a memory log " "and re-read by future analysts, so every word must earn its place." ) @@ -41,7 +43,7 @@ class Reflector: ) -> str: """Single reflection call on the final trade decision with outcome context. - Used by Phase B deferred reflection. The final_trade_decision already + Used when a pending decision is settled. The final_trade_decision already synthesises all analyst insights, so no separate market context is needed. ``benchmark_name`` is the label used for the alpha line (e.g. ``"SPY"`` for US tickers, ``"^N225"`` for ``.T`` listings); defaults to SPY for diff --git a/tradingagents/memory/settlement.py b/tradingagents/memory/settlement.py index 51e658834..6f8354de8 100644 --- a/tradingagents/memory/settlement.py +++ b/tradingagents/memory/settlement.py @@ -1,5 +1,5 @@ """Settling past decisions: once a decision's holding window has traded, score -it against its benchmark and record a reflection on it in the decision log.""" +it against its benchmark and record a reflection on it in the memory log.""" import logging from datetime import datetime, timedelta