- 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
- keep what the code cannot state itself: which session a closeless bar is,
where the drop actually happens, and why the trim must stay unguarded
- drop the field-by-field enumeration, the account of what the previous
behaviour got wrong, and the restatements of adjacent calls
- list kimi-k3 (flagship, 1M context) and kimi-k2.6 (256K, thinking modes) on
both tiers, replacing the custom-only entry
- keep Custom model ID for models newer than the list
- omit the k2.7-code variants: coding specialists, not analysis models
- 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
- four modules wrote to stdout with print() while ten others use a module
logger; a warning printed into the rendered CLI output is effectively
invisible, which is how the trim failure above went unnoticed
- convert the remaining calls to logger.warning with lazy formatting
- remove save_output, SavePathType, decorate_all_methods and get_next_weekday
from utils, none of which had a caller, along with the pandas and typing
imports that only they needed
- 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
- ruff's extend-exclude listed a path that is not part of the repository, so
the entry never matched anything in CI
- keep the generated results/ exclude, which is a real runtime output directory
- 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
- 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