trader.discovery.scan module¶
Turning configured themes — and, since issue #27, analyst-scan candidates — into tradable symbols.
Never raises. A search outage, a rate limit, a malformed response, an analyst lookup timing out — each is caught, logged, and yields fewer candidates rather than an exception. The fixed tickers must keep trading through a discovery failure, which is the same discipline that makes every LLM failure resolve to HOLD.
Failures are isolated per theme and per candidate: one broken theme does not discard another theme’s results, and one symbol whose analyst call fails does not discard its four siblings. The analyst-scan source (issue #27) follows the identical discipline and is isolated from theme search in both directions: a broken scan never loses a theme hit, and vice versa.
- trader.discovery.scan.ANALYST_SCAN_INDEX = 'sp500'¶
trader scan-universe supports only this index today (issue #27’s Open Question #4 defers which others); discover_symbols reads the same one.
- trader.discovery.scan.discover_symbols(*, settings, search, analysts, broker, bars_for, fixed_symbols, now, event_repository=None, index_repository=None, consensus_repository=None)[source]¶
Search every theme, plus (issue #27) whatever the analyst scan found.
index_repository/consensus_repository are optional and, like event_repository, duck-typed rather than imported as concrete persistence classes — discovery/ stays free of a persistence/ dependency. Left None (every existing caller), the analyst-scan source simply contributes nothing, the same degrade themes: [] already gives theme search — neither source blocks the other.
- Parameters:
settings (DiscoverySettings)
bars_for (Callable[[str], Sequence[Bar]])
fixed_symbols (Collection[str])
now (datetime)
- Return type:
list[Candidate]
- trader.discovery.scan.screen_for_entry(symbols, *, settings, analysts, broker, bars_for)[source]¶
Which of these symbols must not be bought, and why.
The operator’s own fixed_tickers run through the same gates a discovered candidate does — “I don’t know that these are good purchases, I only know I am interested in watching them”. A symbol that fails is still evaluated and still records a decision; only the entry is blocked, which is what keeps a watchlist a watchlist.
Returns {SYMBOL: FilterOutcome} for failures only, the whole outcome rather than just its reason text: run_once writes outcome.reason into decisions.reasoning (for a human) and outcome.code into decisions.rejection_reason (for a GROUP BY), and a single object is what keeps those two in sync — two parallel dicts keyed by the same symbols would be the “two fields that can contradict each other” shape this codebase’s own architecture review flags.
Never raises: a screening outage must not block trading, so a symbol whose lookups fail is simply not blocked — the same fail-open direction discover_symbols takes, and the opposite of the exposure seed, which fails closed because guessing low there would spend money.
- Return type:
dict[str, FilterOutcome]