Skip to content

Commit a4701cc

Browse files
committed
fix(memory): add point-in-time trade_date guard to prevent lookahead (#1251)
1 parent a33fd4c commit a4701cc

3 files changed

Lines changed: 29 additions & 11 deletions

File tree

tests/test_memory_log.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -275,14 +275,20 @@ def test_n_same_limit_respected(self, tmp_path):
275275
assert "Buy entry 0" not in ctx
276276
assert "Buy entry 5" in ctx
277277

278-
def test_n_cross_limit_respected(self, tmp_path):
279-
"""Only the n_cross most recent cross-ticker entries are included."""
280-
log = make_log(tmp_path)
281-
for i, ticker in enumerate(["AAPL", "MSFT", "GOOG", "META"]):
282-
_seed_completed(tmp_path, ticker, f"2026-01-{i+1:02d}", f"Buy {ticker}.", "Correct.")
283-
ctx = log.get_past_context("NVDA", n_cross=3)
284-
assert "AAPL" not in ctx
285-
assert "META" in ctx
278+
def test_point_in_time_guard(self, tmp_path):
279+
"""Entries on or after the trade_date are excluded to prevent lookahead."""
280+
log = make_log(tmp_path)
281+
_seed_completed(tmp_path, "NVDA", "2026-01-05", "Decision Jan 5.", "Lesson Jan 5.")
282+
_seed_completed(tmp_path, "NVDA", "2026-01-15", "Decision Jan 15.", "Lesson Jan 15.")
283+
_seed_completed(tmp_path, "AAPL", "2026-01-10", "Decision AAPL Jan 10.", "Lesson AAPL Jan 10.")
284+
_seed_completed(tmp_path, "AAPL", "2026-01-20", "Decision AAPL Jan 20.", "Lesson AAPL Jan 20.")
285+
286+
# Querying with trade_date="2026-01-12" should only see Jan 5 for NVDA and Jan 10 for AAPL
287+
ctx = log.get_past_context("NVDA", trade_date="2026-01-12")
288+
assert "Decision Jan 5" in ctx
289+
assert "Decision Jan 15" not in ctx
290+
assert "AAPL" in ctx
291+
assert "2026-01-20" not in ctx
286292

287293
# No-op when config is None
288294

tradingagents/agents/utils/memory.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,21 @@ def get_pending_entries(self) -> list[dict]:
6767
"""Return entries with outcome:pending (for Phase B)."""
6868
return [e for e in self.load_entries() if e.get("pending")]
6969

70-
def get_past_context(self, ticker: str, n_same: int = 5, n_cross: int = 3) -> str:
71-
"""Return formatted past context string for agent prompt injection."""
70+
def get_past_context(
71+
self,
72+
ticker: str,
73+
n_same: int = 5,
74+
n_cross: int = 3,
75+
trade_date: str = None,
76+
) -> str:
77+
"""Return formatted past context string for agent prompt injection.
78+
79+
If trade_date is provided, only entries whose date is strictly before
80+
trade_date (< trade_date) are included, preventing lookahead in backtests.
81+
"""
7282
entries = [e for e in self.load_entries() if not e.get("pending")]
83+
if trade_date:
84+
entries = [e for e in entries if e.get("date") and str(e["date"]) < str(trade_date)]
7385
if not entries:
7486
return ""
7587

tradingagents/graph/trading_graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ def _run_graph(self, company_name, trade_date, asset_type: str = "stock"):
420420
"""Execute the graph and write the resulting state to disk and memory log."""
421421
# Initialize state — inject memory log context for PM and the
422422
# deterministically resolved instrument identity for all agents.
423-
past_context = self.memory_log.get_past_context(company_name)
423+
past_context = self.memory_log.get_past_context(company_name, trade_date=str(trade_date))
424424
instrument_context = self.resolve_instrument_context(company_name, asset_type)
425425
init_agent_state = self.propagator.create_initial_state(
426426
company_name,

0 commit comments

Comments
 (0)