mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 11:15:24 +03:00
fix(memory): don't settle a decision before its holding window trades
- _fetch_returns settled on min(holding_days, available), so a rerun a day or two after a decision reflected on a 1-2 day partial return as if final - require the full holding window in both the stock and benchmark series before resolving; otherwise leave the entry pending to retry next run - this also makes the #1251 resolution date the full-window date, not a partial bar's #1169
This commit is contained in:
@@ -58,7 +58,7 @@ def _price_df(prices, start="2026-01-05"):
|
||||
"""Minimal DataFrame matching yfinance .history() output shape.
|
||||
|
||||
Uses a DatetimeIndex like real yfinance output, so resolution-date
|
||||
extraction (stock.index[actual_days]) works (#1251).
|
||||
extraction (stock.index[holding_days]) works (#1251).
|
||||
"""
|
||||
idx = pd.date_range(start=start, periods=len(prices), freq="D")
|
||||
return pd.DataFrame({"Close": prices}, index=idx)
|
||||
@@ -529,9 +529,10 @@ class TestDeferredReflection:
|
||||
assert (raw, alpha, days, resolved) == (None, None, None, None)
|
||||
|
||||
def test_fetch_returns_spy_shorter_than_stock(self):
|
||||
"""SPY having fewer rows than the stock must not raise IndexError."""
|
||||
stock_prices = [100.0, 102.0, 104.0, 103.0, 105.0, 106.0]
|
||||
spy_prices = [400.0, 402.0, 403.0]
|
||||
"""SPY having fewer rows than the stock (but still a full window) must
|
||||
not raise IndexError."""
|
||||
stock_prices = [100.0, 102.0, 104.0, 103.0, 105.0, 106.0, 107.0, 108.0] # 8 rows
|
||||
spy_prices = [400.0, 402.0, 403.0, 405.0, 406.0, 407.0] # 6 rows
|
||||
mock_graph = MagicMock(spec=TradingAgentsGraph)
|
||||
with patch("yfinance.Ticker") as mock_ticker_cls:
|
||||
def _make_ticker(sym):
|
||||
@@ -540,9 +541,25 @@ class TestDeferredReflection:
|
||||
return m
|
||||
mock_ticker_cls.side_effect = _make_ticker
|
||||
raw, alpha, days, resolved = TradingAgentsGraph._fetch_returns(mock_graph, "NVDA", "2026-01-05")
|
||||
assert raw is not None and alpha is not None and days is not None
|
||||
assert days == 2
|
||||
assert resolved == "2026-01-07" # 2 sessions after the trade date
|
||||
assert raw is not None and alpha is not None
|
||||
assert days == 5 # full holding window used for both series
|
||||
assert resolved == "2026-01-10"
|
||||
|
||||
def test_fetch_returns_incomplete_window_stays_pending(self):
|
||||
"""#1169: a rerun before the full holding window has traded returns
|
||||
unavailable (all-None) so the entry stays pending, rather than settling
|
||||
on a premature partial return."""
|
||||
stock_prices = [100.0, 102.0, 104.0] # only 3 rows; holding window is 5
|
||||
spy_prices = [400.0, 402.0, 404.0]
|
||||
mock_graph = MagicMock(spec=TradingAgentsGraph)
|
||||
with patch("yfinance.Ticker") as mock_ticker_cls:
|
||||
def _make_ticker(sym):
|
||||
m = MagicMock()
|
||||
m.history.return_value = _price_df(spy_prices if sym == "SPY" else stock_prices)
|
||||
return m
|
||||
mock_ticker_cls.side_effect = _make_ticker
|
||||
result = TradingAgentsGraph._fetch_returns(mock_graph, "NVDA", "2026-01-05")
|
||||
assert result == (None, None, None, None)
|
||||
|
||||
# TradingAgentsGraph._resolve_benchmark — picks index for alpha calc
|
||||
|
||||
@@ -673,6 +690,20 @@ class TestDeferredReflection:
|
||||
assert "+5.0%" in entries[0]["raw"]
|
||||
assert "+2.0%" in entries[0]["alpha"]
|
||||
|
||||
def test_resolve_leaves_premature_entry_pending(self, tmp_path):
|
||||
"""#1169: when the outcome can't be settled yet (_fetch_returns None),
|
||||
the entry stays pending and the reflector is never called."""
|
||||
log = make_log(tmp_path)
|
||||
log.store_decision("NVDA", "2026-01-05", DECISION_BUY)
|
||||
mock_reflector = MagicMock()
|
||||
mock_graph = MagicMock(spec=TradingAgentsGraph)
|
||||
mock_graph.memory_log = log
|
||||
mock_graph.reflector = mock_reflector
|
||||
mock_graph._fetch_returns = MagicMock(return_value=(None, None, None, None))
|
||||
TradingAgentsGraph._resolve_pending_entries(mock_graph, "NVDA")
|
||||
assert len(log.get_pending_entries()) == 1 # still pending
|
||||
mock_reflector.reflect_on_final_decision.assert_not_called()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Portfolio Manager injection: past_context in state and prompt
|
||||
|
||||
Reference in New Issue
Block a user