- a failed fetch and an empty search both returned [], so a 429 rendered as
'no posts found' and the sentiment analyst read throttling as real silence;
when every subreddit was throttled the summary asserted it outright
- a failed fetch now returns None and renders as unavailable, and the summary
only claims silence for subreddits actually searched
- raise the headerless-429 back-off to 60s, which is where a retry starts
succeeding; pay it at most once per run so three throttled subreddits do not
stall the analysis, and match the Retry-After cap to it #1295
- asking the Trader for concrete entry/stop levels invited a percentage
(stop_loss: '15%'), which is not a price and failed the whole structured
parse, dropping the run to a free-text retry
- state the requirement in the prompt and in both field descriptions
- a percentage now nulls that field instead of failing the proposal; it is
never salvaged, since 15% must not become a 15 stop. Human-formatted
prices with a currency symbol or thousands separator parse #1288
- both fundamentals vendors accepted curr_date and ignored it, serving a
present-day company profile into a run dated in the past: yfinance via
Ticker.info, Alpha Vantage via OVERVIEW
- that profile has no historical vintage, not even name/sector/industry (which
move when a company renames or is reclassified), so a past curr_date now
withholds it and says why; live runs are unchanged
- the rule lives once in date_window next to the existing look-ahead helpers,
so switching data_vendors between the two cannot reintroduce the leak, and
the guard runs before the request rather than discarding a paid-for response
- point-in-time fundamentals for a past date already come from the balance
sheet, income statement and cash flow tools, which filter on curr_date #1300
- ElementTree does not resolve external entities, so the reported XXE flag
doesn't apply; the real residual is an unbounded read of untrusted network XML
- cap both the RSS and JSON reads at 5 MiB; overflow degrades to empty / RSS
fallback through the existing failure paths #1206#1276
- the managers reserved Hold only for "genuinely balanced" evidence and were
told to "be decisive", pressuring a directional call on ambiguous, conflicting,
or insufficient inputs; which side it landed on was model-prior-dependent
- allow Hold for balanced, conflicting, ambiguous, or insufficient evidence in
both manager prompts and both structured rating fields, and weigh cases
independent of speaking order; rating definitions and debate ordering unchanged #1196
- a valid Retry-After: 0 means retry at once but was treated as absent
(`or 5.0`) and waited 5s; honour it exactly now
- jitter our own headerless fallback and the inter-subreddit pacing so several
analyses sharing an IP don't retry in lockstep and re-collide on the limit;
keep the single-retry ceiling (more retries can't fix an exhausted IP budget) #1193
- the unconditional realtime pin 400s when curr_date is ahead of FRED's
US-Central date (a live run's local date), which the router then degrades to
a silent DATA_UNAVAILABLE — an Asia/Pacific run loses macro data
- clamp realtime_start/end to min(curr_date, FRED-today) via pytz Chicago;
a past curr_date pins unchanged, so historical look-ahead safety is preserved
- name the vintage in the empty-result message: widening the window can't fix a
series with no vintage coverage #1275
- GPT-5.6 (sol/terra/luna) is GA and OpenAI's recommended default; add it and
make gpt-5.6 (deep) / gpt-5.6-luna (quick) the defaults
- add GLM-5.3 and GLM-5.3-Flash, Zhipu's current flagship line
- the Trader received only the digested investment plan, so its entry / stop /
sizing levels were not anchored to real price structure (ATR, support and
resistance, current price)
- inject the market report and instruct the Trader to take concrete price levels
from it and direction/strategy from the plan; when the market analyst was not
selected the report is empty, so the section and grounding note are omitted #1167
- OpenRouter exposes DeepSeek as deepseek/<id>, which matched neither the exact
IDs nor the patterns, so a thinking model like deepseek/deepseek-v4-flash fell
through to _DEFAULT and had object-form tool_choice forced on it
- strip the official deepseek/ namespace before lookup so it reuses the native
quirks; deepseek/deepseek-chat still keeps tool_choice, and third-party
finetunes on other publishers stay on _DEFAULT #1199
- the latest in-range bar with a NaN close was dropped before the curr_date
cutoff, so the previous trading day looked like the latest; dates were also
compared without timezone normalization
- normalize bar dates and curr_date to naive midnight (per element, so 5-year
ranges spanning DST and non-US positive-offset markets keep their local date),
then raise NoMarketDataError on a missing latest close rather than falling back
- split the fill step (_fill_price_gaps) from date/price normalization so the
latest bar can be inspected before incomplete rows are dropped #1201
- _fetch_returns settled on min(holding_days, available), so a rerun a day or
two after a decision reflected on a 1-2 day partial return as if final
- require the full holding window in both the stock and benchmark series before
resolving; otherwise leave the entry pending to retry next run
- this also makes the #1251 resolution date the full-window date, not a partial
bar's #1169
- inline _in_news_window, a trivial passthrough left from extracting
dataflows.date_window.in_window; call in_window directly
- drop SignalProcessor's orphaned quick_thinking_llm attribute (unused since
rating extraction became a deterministic parse)
- on resume, the CLI and propagate re-passed the initial state to a thread with
an existing checkpoint; nodes do not re-run, but the message reducer appended
the initial messages again, duplicating them in the resumed state
- feed None on resume (checkpoint_input) so LangGraph continues the interrupted
run, and wrap the CLI stream in try/finally so the checkpointer tears down even
if the stream raises
- correct the _fetch_returns docstring to the 4-tuple return #1249
- 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.