4.7 KiB
Architecture Decisions Log
Decision 001: Hybrid LLM Setup (Ollama + OpenRouter)
Date: 2026-03-17 Status: Implemented ✅
Context: Need cost-effective LLM setup for scanner pipeline with different complexity tiers.
Decision: Use hybrid approach:
- quick_think + mid_think:
qwen3.5:27bvia Ollama athttp://192.168.50.76:11434(local, free) - deep_think:
deepseek/deepseek-r1-0528via OpenRouter (cloud, paid)
Config location: tradingagents/default_config.py — per-tier _llm_provider and _backend_url keys.
Consequence: Removed top-level llm_provider and backend_url from config. Each tier must have its own {tier}_llm_provider set explicitly.
Decision 002: Data Vendor Fallback Strategy
Date: 2026-03-17 Status: Implemented ✅
Context: Alpha Vantage free/demo key doesn't support ETF symbols and has strict rate limits. Need reliable data for scanner.
Decision:
route_to_vendor()catchesAlphaVantageError(base class) to trigger fallback, not justRateLimitError.- AV scanner functions raise
AlphaVantageErrorwhen ALL queries fail (not silently embedding errors in output strings). - yfinance is the fallback vendor and uses SPDR ETF proxies for sector performance instead of broken
Sector.overview.
Files changed:
tradingagents/dataflows/interface.py— broadened catchtradingagents/dataflows/alpha_vantage_scanner.py— raise on total failuretradingagents/dataflows/yfinance_scanner.py— ETF proxy approach
Decision 003: yfinance Sector Performance via ETF Proxies
Date: 2026-03-17 Status: Implemented ✅
Context: yfinance.Sector("technology").overview returns only metadata (companies_count, market_cap, etc.) — no performance data (oneDay, oneWeek, etc.).
Decision: Use SPDR sector ETFs as proxies:
sector_etfs = {
"Technology": "XLK", "Healthcare": "XLV", "Financials": "XLF",
"Energy": "XLE", "Consumer Discretionary": "XLY", ...
}
Download 6 months of history via yf.download() and compute 1-day, 1-week, 1-month, YTD percentage changes from closing prices.
File: tradingagents/dataflows/yfinance_scanner.py
Decision 004: Inline Tool Execution Loop for Scanner Agents
Date: 2026-03-17 Status: Implemented ✅
Context: The existing trading graph uses separate ToolNode graph nodes for tool execution (agent → tool_node → agent routing loop). Scanner agents are simpler single-pass nodes — no ToolNode in the graph. When the LLM returned tool_calls, nobody executed them, resulting in empty reports.
Decision: Created tradingagents/agents/utils/tool_runner.py with run_tool_loop() that runs an inline tool execution loop within each scanner agent node:
- Invoke chain
- If tool_calls present → execute tools → append ToolMessages → re-invoke
- Repeat up to
MAX_TOOL_ROUNDS=5until LLM produces text response
Alternative considered: Adding ToolNode + conditional routing to scanner_setup.py (like trading graph). Rejected — too complex for the fan-out/fan-in pattern and would require 4 separate tool nodes with routing logic.
Files:
tradingagents/agents/utils/tool_runner.py(new)- All scanner agents updated to use
run_tool_loop()
Decision 005: LangGraph State Reducers for Parallel Fan-Out
Date: 2026-03-17 Status: Implemented ✅
Context: Phase 1 runs 3 scanners in parallel. All write to shared state fields (sender, etc.). LangGraph requires reducers for concurrent writes — otherwise raises INVALID_CONCURRENT_GRAPH_UPDATE.
Decision: Added _last_value reducer to all ScannerState fields via Annotated[str, _last_value].
File: tradingagents/agents/utils/scanner_states.py
Decision 006: CLI --date Flag for Scanner
Date: 2026-03-17 Status: Implemented ✅
Context: python -m cli.main scan was interactive-only (prompts for date). Needed non-interactive invocation for testing/automation.
Decision: Added --date / -d option to scan command. Falls back to interactive prompt if not provided.
File: cli/main.py
Decision 007: .env Loading Strategy
Date: 2026-03-17 Status: Implemented ✅
Context: load_dotenv() loads from CWD. When running from a git worktree, the worktree .env may have placeholder values while the main repo .env has real keys.
Decision: cli/main.py calls load_dotenv() (CWD) then load_dotenv(Path(__file__).parent.parent / ".env") as fallback. The worktree .env was also updated with real API keys.
Note for future: If .env issues recur, check which .env file is being picked up. The worktree and main repo each have their own .env.