fix(memory): keep a settled decision from being logged twice

- the duplicate guard matched only pending entries, so re-running a settled ticker and date appended a second one
This commit is contained in:
Yijia-Xiao
2026-09-16 20:19:49 +00:00
parent 6436d1ff30
commit 9794a90734
2 changed files with 22 additions and 2 deletions

View File

@@ -136,6 +136,23 @@ class TestTradingMemoryLogCore:
log.store_decision("NVDA", "2026-01-10", DECISION_BUY) log.store_decision("NVDA", "2026-01-10", DECISION_BUY)
assert len(log.load_entries()) == 1 assert len(log.load_entries()) == 1
def test_store_decision_idempotent_after_the_entry_resolves(self, tmp_path):
"""A settled entry still blocks a duplicate.
The guard matched only pending entries, so re-running a ticker and date
whose outcome had already been settled appended a second entry: the same
decision counted twice in past context and in any aggregate over the log.
"""
log = make_log(tmp_path)
log.store_decision("NVDA", "2026-01-10", DECISION_BUY)
log.update_with_outcome("NVDA", "2026-01-10", 0.05, 0.02, 5, "worked", "2026-01-17")
log.store_decision("NVDA", "2026-01-10", DECISION_BUY)
entries = log.load_entries()
assert len(entries) == 1
assert entries[0]["pending"] is False # the settled record is kept, not replaced
def test_batch_update_resolves_multiple_entries(self, tmp_path): def test_batch_update_resolves_multiple_entries(self, tmp_path):
"""batch_update_with_outcomes resolves multiple pending entries in one write.""" """batch_update_with_outcomes resolves multiple pending entries in one write."""
log = make_log(tmp_path) log = make_log(tmp_path)

View File

@@ -36,11 +36,14 @@ class TradingMemoryLog:
"""Append pending entry at end of propagate(). No LLM call.""" """Append pending entry at end of propagate(). No LLM call."""
if not self._log_path: if not self._log_path:
return return
# Idempotency guard: fast raw-text scan instead of full parse # Idempotency guard: fast raw-text scan instead of full parse. Any entry
# for this ticker and date blocks another, pending or settled: a re-run
# after the outcome landed would otherwise count the same decision twice
# in past context and in every aggregate over the log.
if self._log_path.exists(): if self._log_path.exists():
raw = self._log_path.read_text(encoding="utf-8") raw = self._log_path.read_text(encoding="utf-8")
for line in raw.splitlines(): for line in raw.splitlines():
if line.startswith(f"[{trade_date} | {ticker} |") and line.endswith("| pending]"): if line.startswith(f"[{trade_date} | {ticker} |") and line.endswith("]"):
return return
rating = parse_rating(final_trade_decision) rating = parse_rating(final_trade_decision)
tag = f"[{trade_date} | {ticker} | {rating} | pending]" tag = f"[{trade_date} | {ticker} | {rating} | pending]"