- get_past_context returned every resolved lesson regardless of the run date, so
a historical run could learn from an outcome that had not happened yet
- record each resolved entry's resolution date (the last price bar used) and
filter get_past_context(as_of=trade_date) on it for a historical run; a
current-date run passes None so live behavior and pre-migration entries (no
stored resolution date, conservatively excluded from backtests) are unaffected #1251
- checkpoint setup lived only inside propagate(); the CLI streamed the
checkpointer-less graph with no thread_id, so --checkpoint neither saved nor
resumed a run
- extract the lifecycle into reusable begin_checkpoint / end_checkpoint /
clear_checkpoint_on_success (checkpoint_scope wraps them for propagate) and use
them around the CLI stream #1249
- an unrecognizable Portfolio Manager decision was coerced to Hold, emitting a
tradeable neutral signal that masked a parsing failure; a fullwidth colon
(Rating:X) defeated the label regex and hit the same path
- add extract_rating() -> str | None with NFKC normalization and whole-word
matching; the graph signal now yields a REVIEW sentinel (with an is_review
guard) when no rating is found
- parse_rating keeps its silent default for compat callers (e.g. the memory log) #1170
- some model/gateway combinations emit unbounded reasoning/output and hang or
trip an idle timeout (e.g. some deepseek-v4-flash deployments)
- add an opt-in max_tokens config knob + TRADINGAGENTS_MAX_TOKENS, forwarded to
every provider when set (Gemini takes it as max_output_tokens); int-coerced,
rejects non-positive/boolean values #1204
- the first speaker in each debate round received an empty opponent response,
yet the prompt demanded a rebuttal, so models fabricated the other side
- substitute an explicit opening marker when an opponent has not spoken, across
all five debators (bull, bear, and the three risk analysts) #1176
- StockTwits and Reddit were fetched with no date, so a historical run showed
today's chatter as if it were from the as-of date
- pass the analysis window to both fetchers, filter to it, and emit a clear
placeholder when nothing qualifies
- centralize the UTC half-open window rule in dataflows/date_window so news,
StockTwits, and Reddit share one look-ahead-safe filter #1220
- FRED defaults both realtime bounds to today, so historical macro requests
served the latest revision and leaked future information into backtests
- set realtime_start=realtime_end=curr_date on both the metadata and
observations requests #1275
- with_structured_output binds a single tool (the schema), so a primed model emitted
an unknown web_search call and the attempt was discarded for a free-text retry,
costing an extra round trip and the typed output
- drop the tool-range wording from the no-tool sentiment analyst and state the
constraint once via a shared NO_EXTERNAL_TOOLS #1130
- Windows terminals without a console buffer raised NoConsoleScreenBufferError
before the first prompt, surfacing a raw traceback with no guidance
- gate the Windows-only import on sys.platform so a broken prompt_toolkit still
surfaces there, and the handler stays inert on other platforms #1138
- the per-day cache was reused unconditionally, so a run started before the day's
bar was final served that snapshot to every later run, feeding a stale close
into technical analysis
- a present row is not sufficient either, since Yahoo publishes a partial intraday
candle; a TTL now governs every current-day cache while historical caches stay
immutable #1150
- the upper bound was inclusive, so an article stamped exactly midnight after
end_date leaked into a historical run
- flat epoch timestamps were parsed in host-local time and offset-aware stamps had
tzinfo stripped without converting, making filtering machine-dependent
- normalize every operand to UTC and use a half-open [start, end + 1 day) #1126
- refresh the Anthropic lineup to the current GA set (Fable 5, Opus 4.8,
Sonnet 5, Opus 4.7, Haiku 4.5)
- extend the effort gate to single-number Claude 5 IDs (claude-sonnet-5,
claude-fable-5) so their effort setting is honored
- pass the token to ChatBedrockConverse as api_key so langchain-aws prefers bearer
auth and an ambient AWS_PROFILE can't override it; no AWS access keys required #1103
- crypto reached StockTwits as Yahoo's BTC-USD (404) instead of BTC.X, and Reddit
searched the dashed pair that barely matches; both now resolve the base via a
shared crypto_base() helper, restoring crypto sentiment
- also fixes a StockTwits resilience test class that pytest never collected #1113
- checkpoint resume keyed only by ticker+date silently continued the old graph
under a different analyst selection / depth / asset mode; fold a run signature
into the thread id #1089
- add llm_max_retries + TRADINGAGENTS_LLM_MAX_RETRIES, forwarded to every provider
when set (int-coerced, rejects negatives/booleans), so a 429 burst can't abort
a run #1091
- should_continue_debate (2 edges) and should_continue_risk_analysis (3 edges)
each returned more targets than any one edge mapped; a fall-through under
prompt/i18n/refactor drift crashed LangGraph mid-run
- share a complete DEBATE_PATH_MAP / RISK_ANALYSIS_PATH_MAP across every edge #1088
- prompt advertised get_news(query, ...) but the tool takes a ticker, so the
model hallucinated free-text query calls
- advertise get_news(ticker, start_date, end_date) #1116
- the payload is a JSON string, so the dict-only guard skipped filtering and
future-dated reports leaked into historical runs, breaking the #475 guarantee
- parse before filtering; non-JSON bodies and an unset curr_date pass through #1115
- CI gate, unified verified data-access contract, provider and data-vendor registry
- env-over-CLI config precedence, current-generation model catalog
- programmatic report output, plus sweep fixes for data and structured output
The README reproducibility example named gpt-4.1 and the structured-output smoke
script listed gemini-2.5-flash / deepseek-chat / qwen-plus / grok-4 — all retired
from the catalog. Generalize the note and refresh the smoke defaults.
The date hint sat at the end of each analyst's system prompt, after a long
indicator block, so weaker models anchored to their training cutoff when
generating tool-call date ranges. Lead each prompt with it instead.
The per-section markdown report tree was written only by the CLI, so programmatic
(TradingAgentsGraph) runs produced no saved reports.
- Extract the writer into tradingagents/reporting.write_report_tree.
- The CLI's save_report_to_disk delegates to it (no behavior change).
- Add TradingAgentsGraph.save_reports(final_state, ticker) so headless/API callers
get the same report tree, defaulting under results_dir.
The committed lockfile is not consumed by the pip-based install or CI; it only
drifts. A deliberate dependency upgrade, if wanted, is its own scoped PR.
The knob was accepted but inert — analysts run strictly sequentially and the
value was never used. Remove it rather than ship a misleading config key.
Parallel analyst execution is tracked for v0.3 (#634/#671/#487).
A weak model can write a placeholder ('None', 'N/A') into an optional price
field, tripping schema validation. Coerce null-ish strings to None on the
trader/PM float fields; real numeric strings still parse.
Nodes after the trader do not append to messages, so the debug stream reprinted
the same trailing message once per node. Print it only when it changes; the
returned state is unchanged.
- Local servers (LM Studio, vLLM) reject the object-form tool_choice langchain
sends for function calling. The generic openai_compatible provider now binds
the schema as a tool without forcing tool_choice.
- A structured call can return no parsed result (a thinking model answering in
plain text); fall back to free text with a clear reason instead of an opaque
render error.
The yfinance news fetch queried the raw ticker while every other path uses the
canonical symbol, so broker/forex/crypto aliases silently returned no news.
Normalize it (XAUUSD -> GC=F) and keep the user's ticker in the report header.
Optional enrichment vendors (FRED macro, Polymarket events) raised on a bad LLM
indicator, a missing key, or a network blip, which aborted the whole run.
- Router: mark macro_data and prediction_markets optional; a sole-vendor failure
returns a sentinel instead of re-raising. Core categories still raise.
- FRED: reject a descriptive phrase up front and return guidance instead of
400ing the API; an unknown series returns a not-found message, not a crash.
Trim each provider to current-generation models and drop the special-casing
they required:
- OpenAI: remove gpt-4.1 (deprecated; the only non-reasoning model).
- Anthropic: remove Claude Sonnet 4.5 (legacy; the only Sonnet that 400s on effort).
- Google: remove the Gemini 2.5 line (superseded by 3.x).
- Gemini client: drop the integer thinking_budget mapping; 3.x takes the string
thinking_level directly.
Effort/reasoning gates stay as defense in depth for custom model IDs. All kept
IDs verified against live APIs.
Interactive selections and flag defaults overrode TRADINGAGENTS_* env vars.
Rule: an explicit env value or CLI flag wins; otherwise the env-applied
default is kept.
- Research depth: skip the prompt when both round-count env vars are set, and
stop overwriting them (#977).
- Checkpoint: --checkpoint/--no-checkpoint is tri-state; omitting it keeps
TRADINGAGENTS_CHECKPOINT_ENABLED (#976).
- Docker ollama: use TRADINGAGENTS_LLM_PROVIDER + OLLAMA_BASE_URL, not a bare
LLM_PROVIDER the overlay never reads (#975).
- Reasoning/thinking knobs: settable via env; the prompt is skipped when set.
- Effort gating: forward effort only to models that accept it (Anthropic
Opus 4.5+/Sonnet 4.6+, OpenAI reasoning models); drop it elsewhere.
- Boolean env values: raise a named error on invalid input instead of
silently becoming False.
Label each OpenRouter model prompt by mode (quick/deep) like the other
providers, so the two consecutive selections are distinguishable. Populate the
dropdown with the newest models from mainstream chat providers rather than the
universal-newest (which surfaced niche/experimental releases); Custom ID still
reaches anything. Cancelled required prompts now exit cleanly instead of
crashing, and the output-language prompt falls back to English.
Add quality guidance to the narrative field so the sentiment report stays
informative and substantive, with each point adding new signal for the trader.
Verified each provider's hard-coded list against current official docs:
- MiniMax: add MiniMax-M3 (1M ctx, multimodal) as the default; keep M2.7 line.
- Qwen: use the live qwen{3.7,3.6}-{plus,max} IDs.
- GLM: add glm-5.2 as the latest flagship.
- xAI: drop deprecated grok-4-fast-* / grok-4-0709 builds.
- DeepSeek: migrate to deepseek-v4-pro / deepseek-v4-flash (the chat/reasoner
aliases are deprecated 2026-07-24 and now map to V4 Flash).
OpenAI, Anthropic, and Gemini were already current and are unchanged.
With the tree clean, the lint job runs ruff check . on every push and PR rather
than only the files a PR changes, so a lint regression is caught anywhere.
Clear the deferred full-repo lint backlog so the whole tree passes the strict
ruff select (E,W,F,I,B,UP,C4,SIM). Mechanical fixes dominate: import sorting,
pep585/604 annotations, dropped dead imports, and whitespace. The few semantic
changes are behavior-preserving: declare __all__ on the agent_utils and
alpha_vantage re-export hubs; expand 'from x import *' to explicit names; use
immutable tuple defaults instead of mutable list defaults; contextlib.suppress
for try/except/pass; and narrow an over-broad assertRaises.
The output-language instruction is applied across all report-producing agents
(analysts, researchers, risk debators, research manager, trader, portfolio
manager), but nothing enforced it, so agents had silently dropped it before. Add
a parametrized guard asserting each report agent calls get_language_instruction()
so a non-English run stays fully localized and the regression can't recur.
The Responses API exists only on native OpenAI. When the openai provider is
pointed at a custom base_url (a proxy, gateway, or local server that speaks only
Chat Completions), keep the Responses API off so the call does not fail.
A truncated/incomplete chunked response raises http.client exceptions
(IncompleteRead/BadStatusLine) that are not OSErrors, so they bypassed the
existing handler and crashed the analysis. Broaden the catch so the fetch
degrades to its placeholder string like every other transport failure.
The JSON search endpoint is reliably WAF-blocked (403) for public clients, so
probing it on every call doubled request volume against Reddit's per-IP rate
limit and tripped 429 on the RSS fallback, blanking the sentiment feed. Fetch
the Atom/RSS feed directly (JSON kept as an opt-in path that still degrades to
RSS on 403), back off once on a 429 honouring Retry-After, and pace requests a
little wider. Also broaden the error handling to catch http.client chunked
transfer errors (IncompleteRead/BadStatusLine) alongside OSError, which on their
own slipped through and crashed the pipeline.
yfinance intermittently returns a year-old partial frame (e.g. June 2025 rows
for a June 2026 request) that still has rows and a Close, so it passed the
empty-check and silently fed a wrong close price and indicators into the report
(#1021). Add a freshness guard that rejects a frame whose latest row is far
older than the requested date, on both the raw OHLCV path and the indicator
path. It raises the existing NoMarketDataError with a stale-specific detail, so
the vendor router's try-next-vendor and single unavailable-signal handling apply
unchanged; the sentinel now surfaces that detail so the agent reports the
specific reason rather than fabricating a value.
Every condition where a vendor cannot return usable data now derives from a
single VendorError base (errors.py): NoMarketDataError, VendorRateLimitError,
and VendorNotConfiguredError (still a ValueError for back-compat). Vendor-named
errors subclass the generic bases, and the router catches the base types, so a
new vendor needs no new except clause. Not-configured now has explicit
try-next-vendor handling instead of falling through the generic catch-all. The
number of error types tracks the number of distinct router reactions, not the
number of causes.
Surface live, market-implied probabilities for forward-looking events (Fed
decisions, recession, elections, geopolitics, crypto) to the news analyst via a
new get_prediction_markets tool and a prediction_markets vendor category. Backed
by Polymarket's public Gamma API (no key). Results are filtered to open,
forward-looking markets (closed and past-dated events excluded), ranked by
traded volume, and rendered with implied probability, volume, resolution date,
and the recent move. External errors degrade to a clear unavailable message
rather than interrupting the analyst.
Surface Federal Reserve Economic Data (rates, inflation, labor, growth) to the
news analyst via a new get_macro_indicators tool and a macro_data vendor
category. Friendly aliases (cpi, unemployment, fed_funds_rate, 10y_treasury,
yield_curve, ...) map to FRED series IDs; raw series IDs are accepted too. The
report gives the latest value, change over the window, and a recent observation
table. Windowing is lookahead-safe (observation_end = curr_date), missing values
are skipped, and a missing FRED_API_KEY surfaces as a clear not-configured
condition through the vendor router rather than a crash.
Bedrock uses the Converse API (langchain-aws) and the AWS credential chain, so
it has its own client like Anthropic/Google rather than the OpenAI-compatible
registry. langchain-aws is an optional dependency (pip install ".[bedrock]"),
lazy-imported with a clear install hint; importing the package never requires
it. The model name is a Bedrock model ID / inference profile ID.
Each is a one-row entry in the OpenAI-compatible provider registry (base_url,
key env, CLI option); the model is user-specified since they serve many models.