The analyst emitted free-form prose, so its sentiment header varied by
provider and run and downstream consumers needed drifting regex. Extend
the structured-output pattern the trio already uses: a SentimentReport
schema (band + 0-10 score + confidence + narrative) rendered to a
deterministic header, with a free-text fallback for providers that lack
native structured output.
#796
yfinance 1.4.0 regressed the daily-download index to unnamed, so
reset_index() produced an "index" column instead of "Date" and every
stockstats indicator silently failed (no SMA/RSI/MACD/Bollinger/ATR).
Verified across versions: 1.2.0 / 1.3.0 / 1.4.1 name it "Date"; only
1.4.0 is broken. Pin to >=1.4.1 (the upstream fix) and normalize the
date column defensively so a non-"Date" index can't silently drop
indicators on any build.
#890
Reddit blocks the anonymous JSON search endpoint, which silently emptied
the sentiment analyst's Reddit source. Fall back to the public RSS search
feed when JSON fails. RSS lacks score/comment counts, so those posts are
marked "via RSS feed" rather than shown with fake zeros.
#862
Agents had no ground-truth ticker→company mapping, so the market analyst
could pattern-match a price chart to the wrong company (e.g. TOTDY read as
"TotalEnergies"), and every downstream agent inherited the bad framing.
Resolve identity once at run start via a cached, fail-open yfinance lookup
and inject company/sector/exchange into the shared instrument context that
all twelve agents consume, with an explicit do-not-substitute instruction.
Resolution runs on both the propagate() and CLI entry points.
Also replaces the bare "Continue" message-clear placeholder, which some
OpenAI-compatible providers interpreted as the user task, with a
context-anchored placeholder carrying the resolved identity and date.
#814#888
Haiku 4.5 rejects the effort parameter with 400. AnthropicClient.get_llm()
now drops effort when the model isn't in the supported set (Opus 4.5+,
Sonnet 4.5+, mythos-preview). Forward-compat regex catches future
claude-{opus,sonnet}-X-Y releases automatically; Haiku and unknown
models stay excluded conservatively.
14 tests cover Haiku exclusion, current Opus/Sonnet inclusion, future-
version inheritance via pattern, mythos-preview, unknown-default
exclusion, and other passthrough kwargs surviving the effort-skip path.
MinimaxChatOpenAI unconditionally set reasoning_split=True, but the
kwarg is only valid on M2.x reasoning models. The openai SDK's strict
kwarg validation raised TypeError for Coding Plan and any other non-
reasoning MiniMax model.
Adds requires_reasoning_split to ModelCapabilities, gates the payload
injection on it, and only sets True for _MINIMAX_THINKING (M2.x exact
IDs and the ^MiniMax-M\d forward-compat pattern). Same shape as the
existing supports_tool_choice gate.
Regression tests cover both halves: M2.x models still receive the flag,
non-reasoning MiniMax models do not.
- analyst_execution.py: rename "Social Analyst" / "Msg Clear Social"
to "Sentiment Analyst" / "Msg Clear Sentiment" to match v0.2.5.
- conditional_logic.should_continue_social returns the renamed route.
- TradingAgentsGraph.propagate accepts asset_type and threads through
to Propagator.create_initial_state.
- Regression test on the Sentiment Analyst label.
Verified end-to-end (NVDA stock + BTC-USD crypto) on gpt-5.4-mini.
Headline themes in v0.2.5:
- Sentiment Analyst grounded in real data. Renamed from social_media_analyst
and redesigned to pre-fetch Yahoo News, StockTwits, and Reddit before the
LLM is invoked, ending the prior fabrication behavior.
- MiniMax provider with full M2.x catalog and dual-region split. Qwen and
GLM also split into international + China regions with separate API keys
and a clean secondary region prompt in the CLI.
- TRADINGAGENTS_* env-var overlay for DEFAULT_CONFIG with type-aware
coercion; .env loading centralized so every entry point sees the user's
keys. Interactive API-key detection prompts and persists missing keys
to .env on the fly.
- OLLAMA_BASE_URL end-to-end for remote ollama-serve, plus a Custom model
ID option in the Ollama dropdown.
- Configurable news-fetch parameters and configurable alpha benchmark for
non-US tickers (.NS / .T / .HK / .L / .TO / .AX / .BO ship with sensible
regional defaults).
- Multi-language output now propagates to every user-facing agent
(researchers, risk debators, research manager, trader) instead of only
the analysts and portfolio manager.
- Model catalog refresh across all providers (GPT-5.5 frontier, Claude
Opus 4.7, Gemini 3.1 Flash-Lite GA, Grok 4.20, Qwen 3.6 line).
- Capability-dispatch table drives provider-specific structured-output
quirks (DeepSeek V4/reasoner and MiniMax M2.x tool_choice rejection,
MiniMax reasoning_split) so the general client stays clean.
- Fixes: ticker path-traversal validation (security), dotenv loading via
console script, reports save bug, exchange-suffix truncation in the
ticker prompt, Docker permission errors, deepcopy config isolation,
max_recur_limit plumbing, clearer missing-API-key error.
See CHANGELOG.md for the full per-item list with issue/PR references.
SPY was hardcoded as the alpha benchmark in both the return-fetch
path and the reflection label, which produced meaningless alpha for
.NS / .T / .HK / .L / .TO / .AX / .BO listings — FX drift between a
local-currency stock and a USD index dominates the spread.
DEFAULT_CONFIG now exposes benchmark_ticker (explicit override) and
benchmark_map (suffix → regional index, with SPY as the empty-suffix
default). TRADINGAGENTS_BENCHMARK_TICKER joins the env-overlay table.
Trading graph resolves the benchmark once per ticker and threads it
through to both _fetch_returns and reflect_on_final_decision, so the
alpha label reads "Alpha vs ^N225" for Tokyo listings, "Alpha vs ^HSI"
for Hong Kong, etc., instead of the misleading "Alpha vs SPY".
The Required APIs section now mentions the default endpoint,
OLLAMA_BASE_URL for remote ollama-serve, ollama pull, and the
Custom model ID dropdown option, replacing the previous one-liner
that left those details implicit.
Users with other models pulled via `ollama pull` (beyond the three
suggested defaults) can now select "Custom model ID" and type any
model name. Matches the same pattern used for DeepSeek, GLM, Qwen,
and MiniMax — the existing _prompt_custom_model_id flow handles the
"custom" value generically, so this is a one-row catalog addition
plus regression coverage.
OLLAMA_BASE_URL now flows through both the CLI dropdown and the
programmatic client (call-time evaluation so tests behave). After
provider selection, the CLI prints the resolved endpoint and marks
when it came from the env var, plus a soft warning when the URL is
missing a scheme or non-default port. Drops the stale "(local)"
suffix from Ollama model labels since the endpoint is now dynamic.
The agent ingests news, StockTwits, and Reddit, but CLI labels, the
README description, and the legacy shim docstring still framed it as
social-media-only. Updates all user-visible surfaces so the name and
the implementation match.
Adds a canonical PROVIDER_API_KEY_ENV mapping (14 providers including
the three dual-region pairs) and an ensure_api_key() helper. When the
selected provider's key is absent from the environment, the CLI prompts
via questionary.password, writes the value to .env via python-dotenv's
set_key (preserves existing lines), and exports it into os.environ so
the run continues without restart. Wired into cli/main.py right after
the region prompts so qwen-cn, glm-cn, and minimax-cn each check their
own region-specific key. openai_client refactored to consult the same
mapping, eliminating its private duplicate of provider→env-var data.
Adds a single _ENV_OVERRIDES table in default_config.py with type-aware
coercion (str/int/bool), so users can switch llm_provider, deep/quick
models, backend URL, output language, debate rounds, and the checkpoint
flag purely via .env. Centralizes load_dotenv in the package __init__
so the overlay applies for every entry point (CLI, main.py, programmatic).
Drops the hardcoded model assignments and duplicate dotenv loads in
main.py and cli/main.py. Verified live with OpenAI and Gemini.
#602
output_language config now propagates to every user-facing agent.
Previously only the four analysts and portfolio manager respected
the setting, producing partial-localization reports with English
debate text interleaved with non-English analyst sections. Verified
live: 7 agents produce Chinese output when config is set to Chinese.
#575
Per-ticker article limit, global article limit, global lookback
window, and macro query list are now read from get_config()
instead of being hardcoded. Tool wrapper get_global_news passes
None defaults so config overrides flow through the LLM-tool path
too. Macro query defaults broadened from 4 US-centric strings to
5 covering Fed, S&P 500, geopolitics, ECB/BOJ/BOE, commodities.
#606#558#562
Pre-fetches news + StockTwits + Reddit via no-auth public endpoints
and injects structured data blocks into the prompt with professional
analysis instructions. Replaces the prompt-vs-tool mismatch that
caused fabricated social-platform content. Backward-compat alias +
"social" CLI key preserved.
#557#607
Zhipu serves GLM under two brands with separate accounts (Z.AI
international vs BigModel China); the CLI URL pointed at one while
the openai_client default pointed at the other. Split into glm +
glm-cn with secondary region prompt (same UX as Qwen + MiniMax).
Catalog adds glm-5-turbo and glm-4.5-air per docs.z.ai.
Qwen and MiniMax each had two main-dropdown entries (intl + CN);
consolidate to one entry per provider and prompt for region as a
secondary step. Internal provider keys (qwen-cn, minimax-cn) and
endpoint routing unchanged. Add qwen3.6-flash to the Qwen catalog
and drop the version-less aliases (qwen-flash, qwen-plus) that
auto-shift their backing model per Alibaba's docs.
#758
xAI's official docs lead with grok-4.20-reasoning and
grok-4.20-non-reasoning across all SDK examples. Replace the prior
grok-4-1-fast-* entries (hyphens where docs use dots, no literal
code example) with the verified grok-4.20 family. Keep grok-4-0709
and grok-4-fast variants that are still referenced.
gemini-3.1-flash-lite is now GA per ai.google.dev. Use the stable
version (fewer rate limits, stronger compat guarantees) instead of
the -preview suffix. Labels mark preview vs GA explicitly.
Opus 4.7 is the current frontier per platform.claude.com (frontier
category, listed first). Demote Opus 4.6 to second deep-tier slot.
Polish quick-tier labels to match official wording; effort docstring
includes 4.7.
GPT-5.5 (Apr 2026, 1M ctx, $5/$30 per 1M) replaces GPT-5.4 as the
catalog flagship. GPT-5.5 Pro replaces 5.4 Pro in the most-capable
slot. GPT-5.4 demotes to previous-gen cost-effective option.
M2.x tool_choice is enum-only (none/auto), so route through the
no-tool_choice dispatch. MinimaxChatOpenAI injects reasoning_split
so <think> blocks stay out of content. Catalog rounded out to the
full official M2.x lineup plus forward-compat regex.
- dataflows/config: deepcopy + one-level dict merge so a partial
set_config doesn't clobber sibling defaults
- graph: thread max_recur_limit from config to Propagator
- openai_client: name the missing env var in the API-key error
#788#764#680
Two regional endpoints (global api.minimax.io, China api.minimaxi.com)
with separate API keys. Models M2.7 / M2.5 plus -highspeed variants,
204K context. Follows the existing provider-preset pattern.
#789#609#577#546#395#378
useradd --create-home creates /home/appuser but not the
.tradingagents subdir, so cache writes fail with PermissionError
when docker-compose mounts a named volume there (the volume
inherits image-dir ownership on first init).
#627#672#771#690#714#723#780#633#773#631
DeepSeek V4 and reasoner reject tool_choice but accept tools.
Route via a per-model capability table that suppresses tool_choice
for thinking-mode models.
#678#689
langgraph-checkpoint 4.0.3 calls Reviver() at module load without
allowed_objects, printing a pending-deprecation warning at every
CLI start. The upstream patch is merged
(langchain-ai/langgraph#7743) but not released; no app-side seam
fixes it. Install a surgical filter in package init (message regex
+ PendingDeprecationWarning category). Remove when we bump past
langgraph-checkpoint 4.0.3.
The typer.prompt-based input could lose .SH/.SZ/.SS/.HK suffixes on
some shells, so exchange-qualified tickers like 000404.SH arrived
truncated to 000404 and failed downstream lookups. Switch to
questionary.text which reads the raw line; keep SPY-on-empty
behavior and validate the allowed character set (alnum, ._-^) up
to 32 chars.
#770
graph.stream() yields per-node deltas, not the full state. Taking
trace[-1] only captured the last node's contribution, so reports
saved to disk were missing every section except the final decision.
Merge all chunks in both the CLI path and trading_graph._run_graph's
debug branch.
#719#736
load_dotenv() with no arguments walks up from site-packages instead
of the user's CWD, so the installed tradingagents console script
silently misses the project's .env. Pass find_dotenv(usecwd=True)
so the search starts from CWD; same treatment for .env.enterprise.
#726#755#612#747#743#753#729#728#751
Resolves#599: thinking-mode models require reasoning_content to be
echoed back across turns; multi-turn agent runs failed with HTTP 400.
The fix isolates DeepSeek's quirks (reasoning_content round-trip and
the deepseek-reasoner no-tool_choice limitation) into a subclass so
the general OpenAI-compatible client stays untouched. Adds DeepSeek
V4 Pro/Flash to the catalog. 9 new tests; rationale documented in
the class docstrings.
Design adapted from #600; #611 closed in favour of this approach.
The ticker symbol reaches three filesystem-path construction sites
(load_ohlcv cache filename, checkpointer DB path, _log_state results
directory) without validation. A value containing path separators or
"../" escapes the configured cache / checkpoints / results directory.
Two attack vectors:
- Programmatic callers passing arbitrary ticker to propagate()
- Prompt injection via fetched news content steering the LLM into
tool calls with attacker-chosen ticker
Fix: new safe_ticker_component() validator in tradingagents/dataflows/
utils.py applied at all three sites. Allows the standard ticker
character set ([A-Za-z0-9._\-\^], up to 32 chars) and explicitly
rejects dot-only values like "." and ".." which would otherwise pass
the regex but traverse parent directories. Seven test cases cover
the accepted formats (BRK-B, 7203.T, ^GSPC, etc.) and the rejected
inputs (path separators, null bytes, whitespace, empty values,
overlong strings, dot-only values).
Closes#618.
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.
Default config had backend_url='https://api.openai.com/v1' which was
forwarded to every provider client, including Google. ChatGoogleGenerativeAI
constructed requests against that base, producing malformed URLs like
https://api.openai.com/v1/v1beta/models/gemini-2.5-flash:generateContent
that 404 with empty body.
Discovered while running propagate() against Gemini end-to-end. The
structured-output smoke worked because that path constructed the LLM
without going through the factory and without forwarding backend_url;
propagate() goes through TradingAgentsGraph.__init__ which forwards
config['backend_url'] to every provider.
Fix: default to None. Each provider client falls back to its own
endpoint (api.openai.com for OpenAI via _PROVIDER_CONFIG, Gemini's
default for Google, and so on). The CLI flow already sets backend_url
explicitly per provider when the user picks one, so that path is
unchanged.
Verified: full propagate() now passes end-to-end on both
OpenAI gpt-5.4-mini and Gemini gemini-3-flash-preview, with all nine
structure/log/signal checks green for each.
Extends the canonical structured-output pattern from the Portfolio Manager
to the other two decision-making agents. Each of the three agents now
returns a typed Pydantic instance via llm.with_structured_output() in a
single primary call, and a render helper turns the result into the same
markdown shape downstream agents and saved reports already consume.
- ResearchPlan: 5-tier recommendation, conversational rationale, concrete
strategic actions for the trader.
- TraderProposal: 3-tier action (transaction direction is naturally Buy /
Hold / Sell — position sizing happens later at the Portfolio Manager),
reasoning, and optional entry_price / stop_loss / position_sizing.
Rendered output preserves the trailing "FINAL TRANSACTION PROPOSAL:
**BUY/HOLD/SELL**" line for backward compatibility with the analyst
stop-signal text.
- PortfolioDecision: 5-tier rating, executive summary, investment thesis,
optional price_target / time_horizon (unchanged).
The shared try-structured-then-fallback pattern is extracted into
tradingagents/agents/utils/structured.py (bind_structured +
invoke_structured_or_freetext) so all three agents go through the same
code path and log the same warning when a provider lacks structured
output and the agent falls back to free-text generation.
Net effect for users: every saved markdown report (research/manager.md,
trading/trader.md, portfolio/decision.md) now has consistent section
headers across runs and providers, easier to scan.
Net effect for the runtime: the rating extraction round-trip is gone —
the rating comes from the structured response itself, not a second
LLM call. SignalProcessor was already simplified to a heuristic adapter
in the previous commit.
11 new tests in tests/test_structured_agents.py cover the Trader and
Research Manager render functions, structured-output happy paths, and
free-text fallback. Full suite: 88 tests pass in ~2s without API keys.
Three related changes that take the rating pipeline from heuristic-only
to type-safe at the source.
1) Research Manager prompt now uses the same 5-tier scale (Buy /
Overweight / Hold / Underweight / Sell) as the Portfolio Manager,
signal_processing, and the memory log. The prior 3-tier wording
(Buy / Sell / Hold) was the only remaining inconsistency in the
pipeline.
2) Centralise the 5-tier vocabulary and the heuristic prose-rating
parser into tradingagents/agents/utils/rating.py. Both the memory
log and the signal processor now share the same parser instead of
duplicating regex and word-walker logic.
3) Make structured output a first-class part of the Portfolio Manager's
primary call. The PM uses llm.with_structured_output(PortfolioDecision)
so each provider's native structured-output mode (json_schema for
OpenAI/xAI, response_schema for Gemini, tool-use for Anthropic,
function_calling for OpenAI-compatible providers) yields a typed
Pydantic instance directly. A render helper turns that instance back
into the same markdown shape downstream consumers (memory log, CLI
display, saved reports) already expect, so no other code has to know
the PM now produces structured output. Providers without structured
support fall back gracefully to free-text + the deterministic
heuristic.
The previous SignalProcessor had been making a second LLM call to
re-extract the rating from the PM's prose; that round-trip is now
eliminated. SignalProcessor is a thin adapter over parse_rating(),
makes zero LLM calls, and stays for backwards compatibility with
process_signal() callers.
Schema (PortfolioDecision) captures rating + executive_summary +
investment_thesis + optional price_target + time_horizon, with field
descriptions doubling as output instructions. Agent prose remains the
primary artifact; structured output is layered onto the PM only because
it is the one agent whose output has machine-readable downstream
consumers.
15 new tests cover the heuristic parser (markdown-bold edge cases that
had no coverage before), the structured PM happy path, the free-text
fallback path, and that SignalProcessor never invokes the LLM. Full
suite: 77 tests pass in ~2s without API keys.
Long analyses can take many minutes; a crash or interruption forced users
to re-run from scratch and re-pay every LLM call. This adds an opt-in
checkpoint layer backed by per-ticker SQLite databases so the graph
resumes from the last successful node.
How to use:
- CLI: tradingagents analyze --checkpoint
- CLI: tradingagents analyze --clear-checkpoints
- Python: config["checkpoint_enabled"] = True
Lifecycle:
- propagate() recompiles the graph with a SqliteSaver when enabled and
injects a deterministic thread_id derived from ticker+date so the
same ticker+date resumes while a different date starts fresh.
- On successful completion the per-thread checkpoint rows are cleared.
- The context manager is closed in a try/finally so a crash never
leaks the SQLite connection or leaves the graph in checkpoint mode.
Storage: ~/.tradingagents/cache/checkpoints/<TICKER>.db
(override via TRADINGAGENTS_CACHE_DIR).
The checkpointer module is new (tradingagents/graph/checkpointer.py)
and the GraphSetup now returns the uncompiled workflow so it can be
recompiled with a saver when needed.
Adds langgraph-checkpoint-sqlite>=2.0.0 dependency. 3 new tests verify
the crash/resume cycle and that a different date starts fresh.
The previous per-agent BM25 memory was effectively dead code — its only
caller was a commented-out line in main.py. Replace it with a single
append-only markdown decision log driven by the propagate() lifecycle.
Lifecycle:
- store_decision() appends a pending entry at the end of every run
- _resolve_pending_entries() runs at the start of the next same-ticker
run, fetches yfinance returns + alpha vs SPY, and writes one LLM
reflection per resolved entry through an atomic temp-file rename
- Portfolio Manager consumes state["past_context"] (5 most recent
same-ticker entries plus 3 cross-ticker reflection-only excerpts)
Storage at ~/.tradingagents/memory/trading_memory.md
(override: TRADINGAGENTS_MEMORY_LOG_PATH).
Tag schema:
- Pending: [YYYY-MM-DD | TICKER | Rating | pending]
- Resolved: [YYYY-MM-DD | TICKER | Rating | +X.X% | +Y.Y% | Nd]
Removes rank-bm25 dependency and the legacy reflect_and_remember()
plumbing across reflection.py, trading_graph.py, and the agent factories.
49 new tests in tests/test_memory_log.py cover the storage, deferred
reflection, prompt injection, and legacy-removal paths. Full suite
(58 tests) passes in under 2 seconds without API keys.