mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-27 06:56:39 +03:00
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
This commit is contained in:
@@ -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.
|
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`.
|
Override the path with `TRADINGAGENTS_MEMORY_LOG_PATH`.
|
||||||
|
|
||||||
@@ -330,7 +330,7 @@ _, decision = ta.propagate("NVDA", "2026-09-01")
|
|||||||
|
|
||||||
## Evaluating decisions over time
|
## 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
|
```python
|
||||||
from tradingagents.backtest import iter_grid, run_backtest, summarize
|
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
|
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
|
## Reproducibility
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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)
|
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.
|
# context and resolved instrument identity.
|
||||||
init_agent_state = graph.create_run_state(
|
init_agent_state = graph.create_run_state(
|
||||||
selections["ticker"], selections["analysis_date"], selections["asset_type"], portfolio
|
selections["ticker"], selections["analysis_date"], selections["asset_type"], portfolio
|
||||||
|
|||||||
@@ -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
|
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
|
return against the regional benchmark. A backtest is that machinery over a grid
|
||||||
@@ -76,7 +76,7 @@ def _config(tmp_path):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@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)
|
config = _config(tmp_path)
|
||||||
result = run_backtest(["NVDA"], ["2026-01-05", "2026-01-12"], config)
|
result = run_backtest(["NVDA"], ["2026-01-05", "2026-01-12"], config)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
@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()
|
fake = _FakeGraph()
|
||||||
_run_cli(monkeypatch, tmp_path, fake)
|
_run_cli(monkeypatch, tmp_path, fake)
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
One run yields one decision, so it cannot say whether the system decides well.
|
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
|
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
|
rating and later settles it with realized and alpha return against the
|
||||||
instrument's regional benchmark, so there is nothing to record separately.
|
instrument's regional benchmark, so there is nothing to record separately.
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ def run_backtest(
|
|||||||
selected_analysts=("market", "social", "news", "fundamentals"),
|
selected_analysts=("market", "social", "news", "fundamentals"),
|
||||||
run_id: str | None = None,
|
run_id: str | None = None,
|
||||||
) -> BacktestResult:
|
) -> 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
|
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
|
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:
|
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):
|
if isinstance(source, BacktestResult):
|
||||||
path = source.log_path # a run whose cells all failed wrote no log: nothing to score
|
path = source.log_path # a run whose cells all failed wrote no log: nothing to score
|
||||||
elif Path(source).is_file():
|
elif Path(source).is_file():
|
||||||
path = Path(source)
|
path = Path(source)
|
||||||
else:
|
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()
|
entries = TradingMemoryLog({"memory_log_path": str(path)}).load_entries()
|
||||||
# A decision with no readable rating has no direction, so it can neither
|
# 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.
|
# count for nor against the system; it is reported as unscored instead.
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ class TradingAgentsGraph:
|
|||||||
Settles this ticker's pending decisions first, then injects the lessons
|
Settles this ticker's pending decisions first, then injects the lessons
|
||||||
known by the trade date for the Portfolio Manager (#1251) and the
|
known by the trade date for the Portfolio Manager (#1251) and the
|
||||||
resolved instrument identity for every agent (#814). An entry point that
|
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)
|
self.settle_pending(company_name)
|
||||||
return self.propagator.create_initial_state(
|
return self.propagator.create_initial_state(
|
||||||
|
|||||||
@@ -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
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -25,7 +25,7 @@ class TradingMemoryLog:
|
|||||||
# Optional cap on resolved entries. None disables rotation.
|
# Optional cap on resolved entries. None disables rotation.
|
||||||
self._max_entries = cfg.get("memory_log_max_entries")
|
self._max_entries = cfg.get("memory_log_max_entries")
|
||||||
|
|
||||||
# --- Write path (Phase A) ---
|
# --- Write: a run records its decision ---
|
||||||
|
|
||||||
def store_decision(
|
def store_decision(
|
||||||
self,
|
self,
|
||||||
@@ -56,7 +56,7 @@ class TradingMemoryLog:
|
|||||||
with open(self._log_path, "a", encoding="utf-8") as f:
|
with open(self._log_path, "a", encoding="utf-8") as f:
|
||||||
f.write(entry)
|
f.write(entry)
|
||||||
|
|
||||||
# --- Read path (Phase A) ---
|
# --- Read ---
|
||||||
|
|
||||||
def load_entries(self) -> list[dict]:
|
def load_entries(self) -> list[dict]:
|
||||||
"""Parse all entries from log. Returns list of dicts."""
|
"""Parse all entries from log. Returns list of dicts."""
|
||||||
@@ -72,7 +72,7 @@ class TradingMemoryLog:
|
|||||||
return entries
|
return entries
|
||||||
|
|
||||||
def get_pending_entries(self) -> list[dict]:
|
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")]
|
return [e for e in self.load_entries() if e.get("pending")]
|
||||||
|
|
||||||
def get_past_context(
|
def get_past_context(
|
||||||
@@ -114,7 +114,7 @@ class TradingMemoryLog:
|
|||||||
parts.extend(self._format_reflection_only(e) for e in cross)
|
parts.extend(self._format_reflection_only(e) for e in cross)
|
||||||
return "\n\n".join(parts)
|
return "\n\n".join(parts)
|
||||||
|
|
||||||
# --- Update path (Phase B) ---
|
# --- Settle: record a decision's outcome and reflection ---
|
||||||
|
|
||||||
def update_with_outcome(
|
def update_with_outcome(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"""Reflection: a settled decision's outcome turned into a short lesson for later runs."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +11,7 @@ class Reflector:
|
|||||||
self.quick_thinking_llm = quick_thinking_llm
|
self.quick_thinking_llm = quick_thinking_llm
|
||||||
|
|
||||||
def _system_prompt(self, holding_days: int) -> str:
|
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
|
Produces 2-4 sentences of plain prose, compact enough to be re-injected
|
||||||
into future agent prompts without bloating the context window. The
|
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"
|
"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"
|
"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"
|
"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."
|
"and re-read by future analysts, so every word must earn its place."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ class Reflector:
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""Single reflection call on the final trade decision with outcome context.
|
"""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.
|
synthesises all analyst insights, so no separate market context is needed.
|
||||||
``benchmark_name`` is the label used for the alpha line (e.g. ``"SPY"``
|
``benchmark_name`` is the label used for the alpha line (e.g. ``"SPY"``
|
||||||
for US tickers, ``"^N225"`` for ``.T`` listings); defaults to SPY for
|
for US tickers, ``"^N225"`` for ``.T`` listings); defaults to SPY for
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""Settling past decisions: once a decision's holding window has traded, score
|
"""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
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|||||||
Reference in New Issue
Block a user