- the fundamentals brief reached the model as a Python tuple
- analysts no longer emit a trade call that nothing reads
- a report that was not produced says so instead of appearing as a blank section
- holding_period_days sets it; the price request covers the calendar span those trading days occupy
- reflection states the window it judges, so a short one does not read as a failed thesis
- a Sell that fell counted as a miss and a Sell that rose as a hit
- Hold claims no direction, so it reports mean alpha and no hit rate
- the report names the window the outcomes were measured over
- a non-blank message is text, whatever it would mean as a Python literal
- the live layout renders on the alternate screen, so a tall layout does not scroll
- a run with no readable rating says so instead of looking like a normal result
- the state log keeps non-ASCII readable (#1081)
- the labelled rating decides, whatever dash separates it, and a scale the model echoed is not one
- prose naming several ratings is reviewed rather than read as the first word in the text
- an unreadable decision is tagged REVIEW everywhere instead of a tradeable Hold
- unrated decisions are counted apart from the backtest figures
- statements are limited to facts filed by the run's date, at the values filed then
- quarterly figures are picked by span, so a year to date total is not read as a quarter
- a fourth quarter is reported unavailable rather than derived
- opt in with sec_edgar in the fundamental_data chain; a non-filer falls through
- settling a past decision is per entry; a provider error leaves it pending
- an explicitly configured benchmark ticker is normalized like any other symbol (#1075)
- analysts, depth, provider, models and language prefill; prompts still shown
- values no longer offered by the current catalog are dropped
- environment variables keep skipping their step
- analysis stays the bare invocation; backtest runs a ticker and date grid
- selections name the models as the config does
- the pending note prints only when a cell is pending
- run_backtest runs the pipeline per cell into its own decision log and resumes by skipping logged cells
- summarize scores settled cells by rating on realized alpha
- settle_pending settles a ticker whose last decision would otherwise stay open
- PortfolioContext input, rendered once into state and read by the trader, risk and portfolio agents
- --portfolio JSON file on the CLI
- a run without it is never treated as a flat book
- the checkpoint signature keys on the portfolio
- the key prompt created .env with the default umask, typically readable by
other local users
- create it 0600 and tighten an existing file before writing the key; a
read-only file is still updated
- FRED and Alpha Vantage authenticate with a query parameter, and requests
quotes the full URL in HTTP, connection and timeout errors, so the key was
written into any log or traceback that recorded one
- route both vendors through one request helper that re-raises a requests error
as the same class with the key removed, carrying no request, response or
exception chain, each of which still held the URL #1324
- anonymous RSS allows about one request per minute per IP, so fetching each
subreddit separately hit a 429 and a 60s back-off on nearly every run
- search the subreddits as one combined feed at Reddit's full page size and
group posts by the subreddit each entry names; each subreddit keeps its own
limit, and a full page is not taken as evidence of absence
- drop the unused JSON search path, still blocked with a 403, and the
per-subreddit pacing; arguments after subreddits are keyword-only
- the sentiment prompt no longer asks for vote and comment counts, which the
RSS feed does not carry
- Yahoo news and the Reddit and StockTwits feeds serve only recent items, so a
historical window trimmed to nothing was reported as "no news" or "no
posts", and the sentiment analyst scored that silence as a neutral signal
- judge each empty window in one shared rule: it is a real absence only when
the feed's coverage reaches the window's first day and the window ends by
today; otherwise report it unavailable with where coverage starts
- coverage comes from the returned timestamps, which are newest-first on these
feeds, plus Reddit's one-week search lookback; merged global-news searches
prove no continuity and are bounded by the present alone
- state in the sentiment analyst that historical sentiment inputs are not
guaranteed to be point-in-time
- OpenAI: gpt-6-astra
- Google: gemini-3.8-flash and gemini-3.5-flash-lite
- Anthropic: claude-opus-5 and claude-fable-5-1
- models taken out of the picker stay valid through LEGACY_MODELS, so configs
that name them run without an unknown-model warning
- reasoning_effort was forwarded only to IDs matching gpt-5 or the o-series, so
GPT-6 models silently dropped the configured effort; match GPT-5 and later,
with a version boundary so unrelated IDs do not match
- Gemini Pro, 3.8+ and the -latest aliases reject thinking_level "minimal"
with a 400; send it only to numbered Flash models before 3.8 and map it to
"low" elsewhere, since aliases move between generations
- both manager prompts and both rating fields listed "materially conflicting"
as a Hold condition, and every bull/bear and risk debate is conflicting, so
a modest directional edge collapsed to Hold
- state one rule at all four sites: conflict alone is not a reason to Hold,
commit to the stronger side sized by how decisively it wins, and Hold only
when the evidence is still balanced after weighing or too thin for a call
- a modest bull edge now resolves to Overweight; a genuinely balanced debate
still resolves to Hold #1321
- a newest bar with no close made load_ohlcv reject the whole frame, so the
routing layer answered with its no-data sentinel: the caller lost the entire
price history and was told the symbol may be invalid, delisted or not
covered, when only the latest session had not settled
- treat a closeless newest bar as an unsettled session instead. The gap fill
already drops it, here and mid-series alike, so the frame ends at the last
settled bar; only a range with no close anywhere is still no data
- the staleness check keeps deciding whether what remains is recent enough, so
falling back cannot resurrect a long-dead series
- log which bars had no close and which date is being used as the latest close
- the console highlights numbers and URLs, so with colour enabled the rendered
output splits asserted substrings with escape codes and two tests fail
- strip the codes before asserting so the result no longer depends on where the
suite runs
- get_stock requests the full daily series up to today, so trimming to the
requested window is the only thing keeping bars after end_date out of a
historical run
- the trim caught every exception, warned, and returned the untrimmed body, so
a parse failure fed future prices into a backtest with no usable signal that
it had happened
- let a parse failure propagate instead: the routing layer already logs the
vendor failure, falls through to the next vendor, and surfaces the real error
if none can serve
- create_sentiment_analyst pre-fetches news, StockTwits and Reddit before
prompting, and TestSentimentAnalystAgent invoked it unstubbed, so every run
made live requests and a real 429 stalled the suite for minutes
- stub the three sources as the sibling prompt tests already do; the file drops
from ~84s to under a second, and the suite no longer depends on Reddit or
Yahoo being reachable
- 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
- 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
- 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