- 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 field list lived only in the schema, which a provider without structured output never sees
- the trader maps the five-tier recommendation onto its three actions
- 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
- 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