Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

TradingAgents Admin & Architecture Guide

Generated June 2026 from TauricResearch/TradingAgents at 04f434e

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the operational and architectural reference for TradingAgents, a multi-agent LLM financial-trading research framework built on LangGraph. It targets operators deploying the framework, developers extending it, and architects evaluating its design. It documents the agent topology, the LLM-provider abstraction, the data-vendor routing layer, the anti-hallucination mechanisms, configuration, persistence, security posture, and the codebase's internal structure in enough depth that a competent engineer (or AI) could reconstruct the system.

For installation and end-user CLI walkthroughs, see the companion User Guide.

Important

TradingAgents is a research framework. It does not connect to a live brokerage; the Portfolio Manager "executes" against a simulated exchange. Treat its outputs as research artifacts. Trading performance varies with model choice, temperature, data quality, and other non-deterministic factors.


1. Executive Summary

TradingAgents decomposes a trading decision into specialized roles, each backed by a language model, and wires them into a directed graph executed by LangGraph. A run for a given ticker and date proceeds through five stages:

Analyst Team  →  Research Debate  →  Trader  →  Risk Debate  →  Portfolio Manager
(market,         (bull vs bear,      (drafts    (aggressive/    (final 5-tier
 sentiment,       judged by          a trade)    conservative/   Buy/.../Sell
 news,            Research Manager)              neutral, +PM)   decision)
 fundamentals)
Attribute Value
Language Python 3.10+
Orchestration LangGraph StateGraph
Package version 0.2.5
Entry points cli.main:app (console script tradingagents), main.py (programmatic)
LLM providers 11 (OpenAI, Google, Anthropic, xAI, DeepSeek, Qwen, GLM, MiniMax, OpenRouter, Azure, Ollama)
Data vendors yfinance (default), Alpha Vantage; plus StockTwits & Reddit for sentiment
Persistence Markdown decision log + per-ticker SQLite checkpoints
External infra None required beyond the LLM provider + data-vendor HTTP APIs
License See repository

The framework's defining design choices are: a provider-agnostic LLM abstraction that normalizes the quirks of eleven model APIs behind one interface; a vendor-routing data layer with automatic fallback; and a multi-layer anti-hallucination system that deterministically resolves company identity and grounds numeric claims in verified data snapshots.


2. Architecture

2.1 Component map

                          ┌─────────────────────────────────────────┐
   CLI (cli/main.py)      │           TradingAgentsGraph              │
   or main.py             │      (tradingagents/graph/trading_graph)  │
        │                 │                                           │
        │  config + ──────▶  • builds quick/deep LLMs via factory     │
        │  selected           • builds ToolNodes (data tools)         │
        │  analysts           • GraphSetup.setup_graph() → StateGraph │
        │                     • Propagator (initial state)            │
        │                     • ConditionalLogic (edge routing)       │
        │                     • Reflector / SignalProcessor           │
        │                     • TradingMemoryLog (decision log)       │
        └─────────────────┴───────────────────┬───────────────────────┘
                                               │ graph.stream(...)
                ┌──────────────────────────────┼──────────────────────────────┐
                ▼                               ▼                              ▼
       llm_clients/ (factory +         agents/ (node factories         dataflows/ (vendor
       per-provider clients +          + Pydantic schemas)             routing + yfinance/
       capability table)                                               alpha_vantage + reddit/
                                                                       stocktwits + validators)

2.2 The agent graph topology

GraphSetup.setup_graph() (tradingagents/graph/setup.py) compiles a LangGraph StateGraph(AgentState). The topology is built dynamically from the selected analysts:

Analyst loop (per selected analyst, in fixed order market → social → news → fundamentals):

START → [Analyst] ──conditional──▶ tools_<analyst> ──▶ back to [Analyst]
                  └──conditional──▶ Msg Clear <analyst> ──▶ next analyst (or Bull Researcher)

Each analyst is a node that may emit tool calls. ConditionalLogic.should_continue_<analyst> inspects the last message: if it contains tool calls, the graph routes to that analyst's ToolNode (which executes the data tools and loops back); otherwise it routes to a "Msg Clear" node that wipes the message history and advances to the next analyst.

Research debate:

Bull Researcher ⇄ Bear Researcher  → Research Manager → Trader

should_continue_debate alternates between Bull and Bear until investment_debate_state["count"] reaches 2 × max_debate_rounds, then hands to the Research Manager (deep-thinking LLM), who issues the investment plan and routes to the Trader.

Risk debate:

Aggressive → Conservative → Neutral  (round-robin)  → Portfolio Manager → END

should_continue_risk_analysis cycles the three risk debaters until risk_debate_state["count"] reaches 3 × max_risk_discuss_rounds, then hands to the Portfolio Manager (deep-thinking LLM), who issues the final decision and ends the graph.

Note

The analysts and high-volume agents use the quick-thinking LLM; the Research Manager and Portfolio Manager use the deep-thinking LLM. This split is wired in GraphSetup via the analyst_factories and the explicit create_research_manager(self.deep_thinking_llm) / create_portfolio_manager(self.deep_thinking_llm) calls.

2.3 Graph state

The graph state is AgentState (tradingagents/agents/utils/agent_states.py), a MessagesState subclass (so the messages channel gets LangGraph's reducer) extended with:

Field Purpose
company_of_interest The ticker under analysis
asset_type stock or crypto
instrument_context Deterministically resolved ticker identity injected at run start
trade_date Analysis date (data filtered to this date)
market_report, sentiment_report, news_report, fundamentals_report Per-analyst outputs
investment_debate_state InvestDebateState (bull/bear history, judge decision, count)
investment_plan Research Manager's plan
trader_investment_plan Trader's proposal
risk_debate_state RiskDebateState (aggressive/conservative/neutral history, latest speaker, count)
final_trade_decision Portfolio Manager's decision
past_context Memory-log context injected at run start

2.4 Execution lifecycle (TradingAgentsGraph.propagate)

  1. Record the ticker; resolve pending memory-log entries for it (fetch realized returns, generate reflections, batch-write).
  2. If checkpoint_enabled, recompile the graph with a per-ticker SqliteSaver and log whether it is resuming or starting fresh.
  3. Resolve instrument identity deterministically (resolve_instrument_context) and build the initial state (with past_context from the memory log).
  4. Stream (debug) or invoke (non-debug) the graph to completion, merging per-node deltas into a final state.
  5. Write the full state to a JSON log under results_dir/<TICKER>/TradingAgentsStrategy_logs/.
  6. Store the decision as a pending entry in the memory log.
  7. Clear the checkpoint on success.
  8. Return (final_state, process_signal(final_decision)).

3. The LLM Provider Abstraction

tradingagents/llm_clients/ is the heart of the framework's portability. A factory dispatches on provider name; per-provider client classes normalize each API's quirks behind a single BaseLLMClient interface.

3.1 Factory dispatch (factory.py)

create_llm_client(provider, model, base_url=None, **kwargs) -> BaseLLMClient
Provider key(s) Client class API style
openai, xai, deepseek, qwen, qwen-cn, glm, glm-cn, minimax, minimax-cn, ollama, openrouter OpenAIClient (+ DeepSeekChatOpenAI / MinimaxChatOpenAI subclasses) OpenAI-compatible
anthropic AnthropicClient Anthropic Messages
google GoogleClient Gemini
azure AzureOpenAIClient Azure OpenAI

An unsupported provider raises ValueError: Unsupported LLM provider: {provider}. Provider modules are imported lazily so merely importing the factory (e.g. during test collection) does not pull in heavy SDKs or fail on missing keys.

3.2 Content normalization

Several providers (OpenAI Responses API, Gemini 3) return content as a list of typed blocks (reasoning, text, …). Every client subclasses its langchain chat model and overrides invoke() to call normalize_content() (base_client.py), which joins the text blocks into a plain string so downstream agents always see response.content as a string.

3.3 The capability table (capabilities.py)

OpenAI-compatible providers differ in which API parameters they accept. Rather than scattering model-name if ladders through the client code, a declarative table resolves capabilities by exact model ID, then regex pattern, then a default:

Model class supports_tool_choice Structured method Special handling
Default yes function_calling —
DeepSeek chat yes function_calling —
DeepSeek thinking (deepseek-reasoner, deepseek-v4-*, deepseek-v\d*) no function_calling requires reasoning_content round-trip
MiniMax M2.x (MiniMax-M2*) no function_calling requires reasoning_split via extra_body

When a model rejects tool_choice, NormalizedChatOpenAI.with_structured_output() still binds the schema as a tool but suppresses the tool_choice kwarg — mirroring those providers' official tool-calling examples. DeepSeekChatOpenAI re-attaches reasoning_content on the next request (DeepSeek thinking models 400 otherwise); MinimaxChatOpenAI sets reasoning_split=True via extra_body so <think> blocks land in reasoning_details instead of polluting content.

Tip

To onboard a new OpenAI-compatible model with API quirks, add a row to _BY_ID or a pattern to _BY_PATTERN in capabilities.py — not to the client code. New deepseek-v5-* or MiniMax-M3* variants inherit the thinking-mode handling automatically via the forward-compat patterns.

3.4 Provider endpoints and keys

Base URLs live in _PROVIDER_BASE_URL (openai_client.py); the canonical provider→env-var key map lives in api_key_env.py (consumed by both the client and the CLI's key prompt):

Provider Base URL API-key env var
openai (Responses API default) OPENAI_API_KEY
anthropic https://api.anthropic.com/ ANTHROPIC_API_KEY
google (Gemini default) GOOGLE_API_KEY
azure (from AZURE_OPENAI_ENDPOINT) AZURE_OPENAI_API_KEY
xai https://api.x.ai/v1 XAI_API_KEY
deepseek https://api.deepseek.com DEEPSEEK_API_KEY
qwen https://dashscope-intl.aliyuncs.com/compatible-mode/v1 DASHSCOPE_API_KEY
qwen-cn https://dashscope.aliyuncs.com/compatible-mode/v1 DASHSCOPE_CN_API_KEY
glm https://api.z.ai/api/paas/v4/ ZHIPU_API_KEY
glm-cn https://open.bigmodel.cn/api/paas/v4/ ZHIPU_CN_API_KEY
minimax https://api.minimax.io/v1 MINIMAX_API_KEY
minimax-cn https://api.minimaxi.com/v1 MINIMAX_CN_API_KEY
openrouter https://openrouter.ai/api/v1 OPENROUTER_API_KEY
ollama http://localhost:11434/v1 (override via OLLAMA_BASE_URL) none

Important

Dual-region providers (qwen/glm/minimax) keep separate endpoints and separate keys because international and China accounts cannot share credentials. Selecting the wrong region for a key produces auth failures.

3.5 Model catalog & validation

model_catalog.py is the single source of truth for the CLI dropdowns and the validators.py model-name validation. get_known_models() derives the valid set from the catalog. Ollama and OpenRouter accept any model name (validation always returns True); for other providers, an unknown model emits a RuntimeWarning but does not block the run (warn_if_unknown_model).

3.6 Provider-specific thinking configuration

TradingAgentsGraph._get_provider_kwargs() forwards reasoning/thinking settings only to the provider that consumes them:

Provider Config key Forwarded kwarg
google google_thinking_level thinking_level (Gemini 3) or mapped to thinking_budget (Gemini 2.5)
openai openai_reasoning_effort reasoning_effort
anthropic anthropic_effort effort (only for Opus/Sonnet 4.5+; Haiku rejects it)
all temperature temperature (when set)

4. The Data Layer

tradingagents/dataflows/ abstracts market data behind a category-based routing layer with vendor fallback.

4.1 Tool categories and vendor routing (interface.py)

Agent-facing tools (in agents/utils/*_tools.py) are thin @tool wrappers that call route_to_vendor(method, *args). Routing resolves the configured vendor for the tool's category, builds a fallback chain, and tries each vendor in turn.

Category Tools Default vendor
core_stock_apis get_stock_data yfinance
technical_indicators get_indicators yfinance
fundamental_data get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement yfinance
news_data get_news, get_global_news, get_insider_transactions yfinance

get_vendor() checks tool-level config (tool_vendors) first, then category-level (data_vendors). The fallback chain puts configured vendors first, then any remaining available vendor.

4.2 Fallback semantics

route_to_vendor() iterates the fallback chain and handles failures distinctly:

  • AlphaVantageRateLimitError → try the next vendor.
  • NoMarketDataError → remember it; another vendor may have data.
  • Any other exception → remember the first error but keep trying.

If at least one vendor reported "no data", it returns a single explicit NO_DATA_AVAILABLE: ... sentinel instructing the agent not to fabricate values. If no vendor returned data and none reported clean "no data", the first real error is raised. This guarantees an agent never receives a silently-empty result it could hallucinate around.

4.3 Vendors

  • yfinance (y_finance.py, yfinance_news.py, stockstats_utils.py): default. load_ohlcv() downloads a 5-year window, caches per symbol under data_cache_dir, and re-fetches on a poisoned (empty) cache. yf_retry() adds exponential backoff on YFRateLimitError. Technical indicators are computed locally via stockstats.
  • Alpha Vantage (alpha_vantage_*.py): optional, keyed by ALPHA_VANTAGE_API_KEY. Wraps OVERVIEW, BALANCE_SHEET, CASH_FLOW, INCOME_STATEMENT, the indicator endpoints, NEWS_SENTIMENT, and INSIDER_TRANSACTIONS. Raises AlphaVantageNotConfiguredError when unkeyed and AlphaVantageRateLimitError on rate limits.
  • StockTwits (stocktwits.py): keyless per-symbol message stream with bullish/bearish labels; degrades to a placeholder string on any failure.
  • Reddit (reddit.py): keyless search across finance subreddits, with automatic Atom/RSS fallback when the JSON endpoint 403s. RSS-sourced posts omit score/comment counts rather than printing fake zeros.

4.4 Symbol normalization (symbol_utils.py)

normalize_symbol() maps broker/TradingView/MT5-style symbols to Yahoo's convention before any fetch:

User types Yahoo symbol Rule
XAUUSD, GOLD GC=F metals → COMEX/NYMEX futures (alias table)
EURUSD EURUSD=X two known ISO-4217 halves → spot forex =X
BTCUSD BTC-USD known crypto base → -USD
SPX500, US500 ^GSPC index CFD → Yahoo index (alias table)

Add new instruments by appending an alias row — no call-site changes. A failed lookup raises NoMarketDataError, which carries both the requested and canonical symbol for clear messaging.


5. Anti-Hallucination Architecture

This is the framework's most important quality system. Three mechanisms prevent the LLM from inventing companies, prices, or data.

5.1 Deterministic instrument identity

Before any agent runs, resolve_instrument_identity() (agent_utils.py) performs a cached, fail-open yfinance lookup of the ticker's real company name, sector, industry, and exchange. build_instrument_context() injects this into AgentState.instrument_context, instructing every agent to use the exact ticker and not substitute a different company. This fixed the failure mode where the Market Analyst would pattern-match a chart to a narrative and invent an identity that cascaded downstream (issue #814). If yfinance is unavailable, the function returns {} and the run falls back to ticker-only context rather than failing.

5.2 Verified market snapshot

build_verified_market_snapshot() (market_data_validator.py) computes a deterministic, LLM-free ground-truth snapshot: the latest OHLCV row on or before the analysis date, a fixed set of indicators (SMA/EMA/RSI/Bollinger/MACD/ATR via stockstats), and recent closes. The Market Analyst is told to treat this as the source of truth for any exact numeric claim and to flag — not reconcile — conflicting tool output. This stops confabulated price levels and "historically validated bounces" (issue #830).

5.3 NO_DATA_AVAILABLE sentinel

As described in 4.2, the vendor router returns an explicit "data unavailable, do not fabricate" string rather than an empty result when a symbol has no data anywhere.

5.4 Look-ahead-bias prevention

Backtests must not see future data. Every data path filters to the analysis date: load_ohlcv() drops rows after curr_date; filter_financials_by_date() and _filter_reports_by_date() drop fiscal periods after it; the verified-snapshot path re-applies the cutoff defensively.


6. Structured Output & the Decision Schema

The three decision-making agents (Research Manager, Trader, Portfolio Manager) produce typed Pydantic output via llm.with_structured_output(Schema), then render it back to markdown so the rest of the system (memory log, CLI, saved reports) is unchanged.

Agent Schema Key fields
Research Manager ResearchPlan recommendation (5-tier), rationale, strategic_actions
Trader TraderProposal action (3-tier Buy/Hold/Sell), reasoning, optional entry_price/stop_loss/position_sizing
Portfolio Manager PortfolioDecision rating (5-tier), executive_summary, investment_thesis, optional price_target/time_horizon
Sentiment Analyst SentimentReport overall_band (6-tier), overall_score (0–10), confidence, narrative

The structured.py helper centralizes the pattern: bind_structured() wraps the LLM (returning None if the provider lacks structured output, logging a warning), and invoke_structured_or_freetext() runs the structured call and falls back to a plain llm.invoke() on any failure — so a weak model or transient error never blocks the pipeline.

SignalProcessor.process_signal() extracts the final rating from the rendered markdown using the deterministic parse_rating() heuristic — no extra LLM call — because the PM's structured output guarantees a parseable **Rating**: X line.

Note

The five-tier scale (Buy / Overweight / Hold / Underweight / Sell) is centralized in agents/utils/rating.py and used identically by the Research Manager, Portfolio Manager, signal processor, and memory log, preventing vocabulary drift.


7. Persistence

7.1 Decision log (TradingMemoryLog)

An append-only markdown log (memory.py) at memory_log_path (default ~/.tradingagents/memory/trading_memory.md). Each decision is appended as a tagged block:

[2026-01-15 | NVDA | Buy | pending]

DECISION:
...portfolio manager decision markdown...

<!-- ENTRY_END -->

The HTML-comment separator cannot appear in LLM prose, making it a safe delimiter. On the next same-ticker run, _resolve_pending_entries() fetches the realized raw and alpha return, generates a reflection via the Reflector, and rewrites the tag with the figures ([date | ticker | rating | +4.2% | +1.1% | 5d]) plus a REFLECTION: section — using an atomic temp-file + os.replace() write. get_past_context() injects the most recent same-ticker decisions and cross-ticker lessons into the PM prompt.

Tip

Set memory_log_max_entries to cap the number of resolved entries; the oldest are rotated out once the cap is exceeded. Pending entries are never pruned. None (default) disables rotation.

7.2 Checkpoint resume (checkpointer.py)

Opt-in via checkpoint_enabled / --checkpoint. Uses LangGraph's SqliteSaver with one SQLite database per ticker under data_cache_dir/checkpoints/<TICKER>.db, so concurrent tickers do not contend. The thread ID is a SHA-256 of ticker:date (truncated to 16 chars), so the same ticker+date resumes and a new date starts fresh. Checkpoints are cleared on successful completion. clear_all_checkpoints() deletes every DB; --clear-checkpoints invokes it before a run.

Caution

Ticker values are validated through safe_ticker_component() before being used as a filename in the checkpoint, cache, and results paths — see Section 9. A malicious ticker cannot escape its intended directory.


8. Configuration Mastery

8.1 DEFAULT_CONFIG reference

Defined in tradingagents/default_config.py. Copy it (DEFAULT_CONFIG.copy()) and override individual keys.

Key Default Purpose
results_dir ~/.tradingagents/logs Per-run state logs (env: TRADINGAGENTS_RESULTS_DIR)
data_cache_dir ~/.tradingagents/cache Price cache + checkpoints (env: TRADINGAGENTS_CACHE_DIR)
memory_log_path ~/.tradingagents/memory/trading_memory.md Decision log (env: TRADINGAGENTS_MEMORY_LOG_PATH)
memory_log_max_entries None Cap on resolved log entries; None = no rotation
llm_provider openai Provider key
deep_think_llm gpt-5.5 Research Manager + Portfolio Manager model
quick_think_llm gpt-5.4-mini Analysts/researchers/trader model
backend_url None Override endpoint; None = provider default
google_thinking_level None Gemini thinking level
openai_reasoning_effort None OpenAI reasoning effort
anthropic_effort None Anthropic effort
temperature None Cross-provider sampling temperature
checkpoint_enabled False Enable resume checkpoints
output_language English Report language (debate stays English)
max_debate_rounds 1 Research-team debate rounds
max_risk_discuss_rounds 1 Risk-team debate rounds
max_recur_limit 100 LangGraph recursion limit
analyst_concurrency_limit 1 Analyst execution concurrency
news_article_limit 20 Max articles per ticker
global_news_article_limit 10 Max macro articles
global_news_lookback_days 7 Macro news lookback
global_news_queries (5 macro queries) Macro headline search terms
data_vendors all yfinance Per-category vendor
tool_vendors {} Per-tool vendor override (wins over category)
benchmark_ticker None Force a single alpha benchmark
benchmark_map (suffix→index map) Per-exchange alpha benchmark

8.2 Environment-variable overrides

_apply_env_overrides() overlays TRADINGAGENTS_* variables onto DEFAULT_CONFIG at import, coercing to the existing default's type (bool/int/float/str). To expose a new key for env override, add a row to _ENV_OVERRIDES — no entry-point changes needed.

TRADINGAGENTS_LLM_PROVIDER=anthropic \
TRADINGAGENTS_DEEP_THINK_LLM=claude-opus-4-8 \
TRADINGAGENTS_MAX_DEBATE_ROUNDS=3 \
tradingagents analyze

Setting provider/models/backend-URL/language via env also skips the matching CLI prompt, enabling unattended runs.

8.3 Config mutation semantics (dataflows/config.py)

set_config() merges dict-valued keys (e.g. data_vendors) one level deep, so a partial update like {"data_vendors": {"core_stock_apis": "alpha_vantage"}} preserves the other nested defaults; scalar keys are replaced. This fixed config state leaking between runs when sub-dicts were mutated (#788).

8.4 Alpha benchmark configuration

_resolve_benchmark() picks the alpha baseline: benchmark_ticker overrides everything; otherwise benchmark_map matches the ticker's exchange suffix (.T→^N225, .HK→^HSI, .L→^FTSE, etc.), falling back to SPY for US tickers. This replaced a hardcoded SPY benchmark that let FX drift dominate alpha for non-USD listings (#628, #684).


9. Security

Important

This section reflects the framework's actual security posture as implemented in source. Review it before deploying in any shared or automated environment.

9.1 Path-traversal hardening

safe_ticker_component() (dataflows/utils.py) validates every ticker used as a filesystem path component (cache files, checkpoint DBs, results directories). It rejects tickers that are empty, exceed a length cap, contain characters illegal in a path, or consist solely of dots — raising ValueError. This prevents a malicious ticker like ../../etc/x from escaping its intended directory (#618). Applied in load_ohlcv(), _db_path(), and _log_state().

9.2 Secrets handling

  • API keys are read from environment variables (loaded from .env / .env.enterprise at package import via python-dotenv). load_dotenv uses override=False, so shell-exported values are never clobbered.
  • The CLI's ensure_api_key() prompts for a missing key with questionary.password (masked input) and persists it to .env via set_key.
  • Missing keys raise a descriptive ValueError naming the exact env var to set (#680).

Caution

Keys are written to a plaintext .env in the working directory. Ensure .env is git-ignored (it is, by repo convention) and protected by filesystem permissions. In Docker, the .env is passed via env_file and never baked into the image.

9.3 Network egress

The framework makes outbound HTTPS calls to: the selected LLM provider, Yahoo Finance, Alpha Vantage (if keyed), StockTwits, Reddit, and api.tauric.ai/v1/announcements (CLI welcome panel, 1s timeout, silent on failure). All data fetchers degrade gracefully on network failure rather than crashing.

9.4 Untrusted-input surface

LLM output is treated as untrusted: tool arguments are validated at the tool boundary (e.g. invalid indicator names raise ValueError and are caught), and the structured-output schemas constrain the decision agents. The verified-snapshot and instrument-identity layers prevent the model from acting on fabricated data.

9.5 Docker runtime

The Dockerfile runs as a non-root appuser and pre-creates the writable ~/.tradingagents directory with correct ownership, resolving first-run permission errors (#519, #771).


10. Operations

10.1 Deployment models

Model How
Local CLI pip install . then tradingagents
Docker (cloud LLM) docker compose run --rm tradingagents
Docker (local Ollama) docker compose --profile ollama run --rm tradingagents-ollama
Programmatic / embedded Import TradingAgentsGraph, call .propagate()
Unattended / batch Set TRADINGAGENTS_* env vars to skip all prompts

10.2 Runtime artifacts

Path Contents
~/.tradingagents/logs/<TICKER>/TradingAgentsStrategy_logs/full_states_log_<date>.json Full final state per run
~/.tradingagents/cache/*.csv Cached OHLCV per symbol
~/.tradingagents/cache/checkpoints/<TICKER>.db Resume checkpoints
~/.tradingagents/memory/trading_memory.md Decision log
./reports/<TICKER>_<timestamp>/ User-saved report tree (when prompted)

10.3 Observability

The CLI's StatsCallbackHandler (a langchain BaseCallbackHandler) counts LLM calls, tool calls, and input/output tokens (thread-safe via a lock) and surfaces them in the footer. The AnalystWallTimeTracker reports per-analyst wall time. A message_tool.log is written per run capturing every message and tool call.

10.4 Cost & latency controls

  • Lower max_debate_rounds / max_risk_discuss_rounds (Shallow depth = 1) to reduce LLM calls.
  • Reduce news_article_limit / global_news_article_limit to shrink agent prompt size.
  • Use cheaper quick-thinking models for analysts; reserve frontier models for the deep-thinking decision agents.
  • Use a local Ollama provider to eliminate per-token cost.

10.5 Backup & recovery

The decision log and price cache are plain files under ~/.tradingagents/; back up that directory to preserve learned context. The Docker Compose setup persists it in the tradingagents_data named volume. Checkpoints are disposable (cleared on success); they exist only to survive a crash mid-run.


11. Repository Structure

TradingAgents/
├── main.py                       # programmatic example entry point
├── pyproject.toml                # build + deps + console script
├── Dockerfile, docker-compose.yml
├── .env.example, .env.enterprise.example
├── cli/                          # interactive Typer CLI
│   ├── main.py                   # app, run_analysis(), live dashboard, analyze command
│   ├── utils.py                  # questionary prompts, model menus, key handling
│   ├── models.py                 # AnalystType, AssetType enums
│   ├── stats_handler.py          # LLM/tool/token callback counter
│   ├── announcements.py, config.py
│   └── static/welcome.txt
└── tradingagents/
    ├── __init__.py               # loads .env, suppresses upstream warnings
    ├── default_config.py         # DEFAULT_CONFIG + env-override overlay
    ├── graph/                    # orchestration
    │   ├── trading_graph.py      # TradingAgentsGraph (the orchestrator)
    │   ├── setup.py              # GraphSetup.setup_graph() builds the StateGraph
    │   ├── conditional_logic.py  # edge routing (debate/risk loops)
    │   ├── propagation.py        # initial-state construction
    │   ├── analyst_execution.py  # analyst node specs + wall-time tracker
    │   ├── checkpointer.py       # per-ticker SqliteSaver
    │   ├── reflection.py         # post-outcome reflection LLM call
    │   └── signal_processing.py  # rating extraction (no LLM)
    ├── llm_clients/              # provider abstraction
    │   ├── factory.py, base_client.py
    │   ├── openai_client.py, anthropic_client.py, google_client.py, azure_client.py
    │   ├── capabilities.py       # declarative per-model quirk table
    │   ├── model_catalog.py, validators.py, api_key_env.py
    ├── agents/                   # node factories + schemas
    │   ├── __init__.py           # exports every create_* factory
    │   ├── schemas.py            # Pydantic decision schemas + renderers
    │   ├── analysts/, researchers/, managers/, risk_mgmt/, trader/
    │   └── utils/                # agent_states, agent_utils, tools, memory, rating, structured
    └── dataflows/                # data layer
        ├── interface.py          # vendor routing + fallback
        ├── y_finance.py, yfinance_news.py, stockstats_utils.py
        ├── alpha_vantage*.py
        ├── reddit.py, stocktwits.py
        ├── market_data_validator.py, symbol_utils.py, utils.py, config.py

12. Extending the Framework

To add… Do this
A new LLM provider (OpenAI-compatible) Add it to _OPENAI_COMPATIBLE and _PROVIDER_BASE_URL, register its key in PROVIDER_API_KEY_ENV, and add models to MODEL_OPTIONS.
A new LLM provider (custom API) Add a BaseLLMClient subclass and a branch in create_llm_client().
A model with API quirks Add a row to capabilities._BY_ID or a pattern to _BY_PATTERN.
A new data vendor Implement the vendor functions and register them per method in VENDOR_METHODS.
A new symbol mapping Add a row to _ALIASES (or rely on the forex/crypto rules) in symbol_utils.py.
A new env-overridable config key Add a row to _ENV_OVERRIDES in default_config.py.
A new data tool Add a @tool wrapper in agents/utils/, register it in a category in TOOLS_CATEGORIES + VENDOR_METHODS, and add it to the relevant ToolNode in _create_tool_nodes().

13. Code Quality & Patterns

  • Declarative tables over conditionals. Provider quirks (capabilities.py), env overrides (_ENV_OVERRIDES), symbol aliases (_ALIASES), and model catalogs are data, not code branches — new cases are table rows.
  • Single source of truth. The 5-tier rating (rating.py), the provider→key map (api_key_env.py), and the model catalog (model_catalog.py) each live in exactly one place to prevent drift.
  • Fail-open data, fail-loud config. Network/data layers degrade gracefully (placeholders, fallbacks); missing config (API keys) fails loudly with an actionable message.
  • Atomic writes. The decision log uses temp-file + os.replace() so a crash never corrupts it.
  • Lazy imports in the factory keep test collection and partial use from pulling in heavy SDKs.
  • UTF-8 everywhere. All file I/O passes explicit encoding="utf-8" (fixed Windows cp1252 failures, #543/#550/#576).

14. Known Issues & Community-Reported Behavior

Drawn from the project's issue tracker; useful for operators triaging support.

Issue Status Notes
Reddit 403 blocked (#935) Mitigated Automatic RSS fallback; scores/comments omitted
Local Ollama 404 / connection (#664, #843, #760) Configuration Set OLLAMA_BASE_URL, pull the model, use exact tag
Ollama model-tag mismatch (#657) Configuration glm-4.7-flash-30b:latest vs glm-4.7-flash:latest — use ollama list
OpenRouter "no OpenAI key" (#655) Fixed Each provider names its own key in the error
MiniMax connection errors (#851) Open Reasoning-split + capability handling added; verify region/key
DeepSeek Chinese output when localized (#740) Addressed Multi-language now covers every user-facing agent
Non-US benchmark broke alpha (#628) Fixed Per-exchange benchmark_map
Reports not saving end-to-end (#719, #736) Fixed Streamed chunks merged before save

Architectural notes & tech debt observed in source

  • cli/main.py mixes the live-dashboard rendering, the streaming event loop, and report I/O in one ~1,300-line module; the display logic could be extracted from the orchestration loop.
  • tradingagents/__init__.py carries two upstream-warning suppressions (langchain-core pending-deprecation, langgraph-checkpoint Reviver) explicitly flagged for removal once the upstream fixes ship — track those dependency bumps.
  • The reflect_and_remember() BM25 memory system was removed in 0.2.4 in favor of the decision log; ensure no external callers still expect it.

15. Quick Reference

Decision flow

START → analysts (loop with tools) → Bull⇄Bear → Research Manager
      → Trader → Aggressive→Conservative→Neutral (loop) → Portfolio Manager → END

Debate-round math

  • Research debate ends at count ≥ 2 × max_debate_rounds.
  • Risk debate ends at count ≥ 3 × max_risk_discuss_rounds.

Provider key env vars

OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, AZURE_OPENAI_API_KEY, XAI_API_KEY, DEEPSEEK_API_KEY, DASHSCOPE_API_KEY, DASHSCOPE_CN_API_KEY, ZHIPU_API_KEY, ZHIPU_CN_API_KEY, MINIMAX_API_KEY, MINIMAX_CN_API_KEY, OPENROUTER_API_KEY, ALPHA_VANTAGE_API_KEY

Config env overrides

TRADINGAGENTS_LLM_PROVIDER, TRADINGAGENTS_DEEP_THINK_LLM, TRADINGAGENTS_QUICK_THINK_LLM, TRADINGAGENTS_LLM_BACKEND_URL, TRADINGAGENTS_OUTPUT_LANGUAGE, TRADINGAGENTS_MAX_DEBATE_ROUNDS, TRADINGAGENTS_MAX_RISK_ROUNDS, TRADINGAGENTS_CHECKPOINT_ENABLED, TRADINGAGENTS_BENCHMARK_TICKER, TRADINGAGENTS_TEMPERATURE, TRADINGAGENTS_RESULTS_DIR, TRADINGAGENTS_CACHE_DIR, TRADINGAGENTS_MEMORY_LOG_PATH

Key exceptions

Exception Raised when
ValueError: Unsupported LLM provider Unknown provider key
ValueError: API key for provider '…' is not set Missing provider key
AlphaVantageNotConfiguredError Alpha Vantage selected without ALPHA_VANTAGE_API_KEY
AlphaVantageRateLimitError Alpha Vantage rate limit hit (triggers fallback)
NoMarketDataError A vendor returned no rows for a symbol
ValueError (ticker validation) Ticker fails safe_ticker_component path checks