mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-05-01 14:33:10 +03:00
chore: release v0.2.4 — structured agents, checkpoint, memory log, providers
This release bundles substantial work since v0.2.3: - Structured-output Research Manager, Trader, and Portfolio Manager (canonical with_structured_output pattern, single LLM call per agent, rendered markdown preserves the existing report shape). - LangGraph checkpoint resume for crash recovery (--checkpoint flag). - Persistent decision log replacing the per-agent BM25 memory, with deferred reflection driven by yfinance returns + alpha vs SPY. - DeepSeek, Qwen, GLM, and Azure OpenAI provider support; dynamic OpenRouter model selection. - Docker support; cache and logs moved to ~/.tradingagents/ to fix Docker permission issues. - Windows UTF-8 encoding fix on every file I/O site. - 5-tier rating consistency (Buy / Overweight / Hold / Underweight / Sell) across Research Manager, Portfolio Manager, signal processor, memory log. Plus the small quality items in this commit: 1. Suppress noisy Pydantic serializer warnings from OpenAI Responses-API parse path by defaulting structured-output to method="function_calling" (root-cause fix, not a warnings filter — same typed result, no warnings). 2. Ship scripts/smoke_structured_output.py so contributors can verify their provider's structured-output path with one command. 3. Add opt-in memory_log_max_entries config — when set, oldest resolved memory log entries are pruned once the cap is exceeded; pending entries (unresolved) are never pruned. 4. backend_url default changed from the OpenAI URL to None so the per-provider client falls back to its native endpoint instead of leaking OpenAI's URL into Gemini / other clients. CHANGELOG.md added with the full v0.2.4 entry. 92 tests pass without API keys.
This commit is contained in:
@@ -291,6 +291,59 @@ class TestTradingMemoryLogCore:
|
||||
assert log.load_entries() == []
|
||||
assert log.get_past_context("NVDA") == ""
|
||||
|
||||
# Rotation: opt-in cap on resolved entries
|
||||
|
||||
def test_rotation_disabled_by_default(self, tmp_path):
|
||||
"""Without max_entries, all resolved entries are kept."""
|
||||
log = make_log(tmp_path)
|
||||
for i in range(7):
|
||||
_resolve_entry(log, "NVDA", f"2026-01-{i+1:02d}", DECISION_BUY, f"Lesson {i}.")
|
||||
assert len(log.load_entries()) == 7
|
||||
|
||||
def test_rotation_prunes_oldest_resolved(self, tmp_path):
|
||||
"""When max_entries is set and exceeded, oldest resolved entries are pruned."""
|
||||
log = TradingMemoryLog({
|
||||
"memory_log_path": str(tmp_path / "trading_memory.md"),
|
||||
"memory_log_max_entries": 3,
|
||||
})
|
||||
# Resolve 5 entries; rotation should keep only the 3 most recent.
|
||||
for i in range(5):
|
||||
_resolve_entry(log, "NVDA", f"2026-01-{i+1:02d}", DECISION_BUY, f"Lesson {i}.")
|
||||
entries = log.load_entries()
|
||||
assert len(entries) == 3
|
||||
# Confirm the OLDEST were dropped, not the newest.
|
||||
dates = [e["date"] for e in entries]
|
||||
assert dates == ["2026-01-03", "2026-01-04", "2026-01-05"]
|
||||
|
||||
def test_rotation_never_prunes_pending(self, tmp_path):
|
||||
"""Pending entries (unresolved) are kept regardless of the cap."""
|
||||
log = TradingMemoryLog({
|
||||
"memory_log_path": str(tmp_path / "trading_memory.md"),
|
||||
"memory_log_max_entries": 2,
|
||||
})
|
||||
# 3 resolved + 2 pending. With cap=2, only 2 resolved survive; both pending stay.
|
||||
for i in range(3):
|
||||
_resolve_entry(log, "NVDA", f"2026-01-{i+1:02d}", DECISION_BUY, f"Resolved {i}.")
|
||||
log.store_decision("NVDA", "2026-02-01", DECISION_BUY)
|
||||
log.store_decision("NVDA", "2026-02-02", DECISION_OVERWEIGHT)
|
||||
# Trigger rotation by resolving one more entry — pending entries must stay.
|
||||
_resolve_entry(log, "NVDA", "2026-01-04", DECISION_BUY, "Resolved 3.")
|
||||
entries = log.load_entries()
|
||||
pending = [e for e in entries if e["pending"]]
|
||||
resolved = [e for e in entries if not e["pending"]]
|
||||
assert len(pending) == 2, "pending entries must never be pruned"
|
||||
assert len(resolved) == 2, f"expected 2 resolved after rotation, got {len(resolved)}"
|
||||
|
||||
def test_rotation_under_cap_is_noop(self, tmp_path):
|
||||
"""No rotation when resolved count <= max_entries."""
|
||||
log = TradingMemoryLog({
|
||||
"memory_log_path": str(tmp_path / "trading_memory.md"),
|
||||
"memory_log_max_entries": 10,
|
||||
})
|
||||
for i in range(3):
|
||||
_resolve_entry(log, "NVDA", f"2026-01-{i+1:02d}", DECISION_BUY, f"Lesson {i}.")
|
||||
assert len(log.load_entries()) == 3
|
||||
|
||||
# Rating parsing: markdown bold and numbered list formats
|
||||
|
||||
def test_rating_parsed_from_bold_markdown(self, tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user