trader.persistence.analyst_consensus module

Repository for analyst_consensus_history (issue #27).

Append-only writes from trader scan-universe; reads answer “what is the newest-enough opinion for each of these symbols”, which is the only question discover_symbols’s analyst-scan candidate source and scan-universe’s own freshness skip ever ask.

class trader.persistence.analyst_consensus.AnalystConsensusRepository(session_factory)[source]

Bases: object

Writes and reads analyst_consensus_history.

Parameters:

session_factory (sessionmaker[Session])

append(symbol, opinion, *, fetched_at, source)[source]

Record one fetch. Returns the new row id.

Always inserts — never upserts. A later fetch is evidence the consensus moved, not a correction of the earlier one.

Parameters:
  • symbol (str)

  • opinion (AnalystOpinion)

  • fetched_at (datetime)

  • source (str)

Return type:

int

latest_for_symbols(symbols, *, max_age, now)[source]

The newest-per-symbol opinion, for symbols fetched within max_age.

A symbol with no row, or only a stale one, is simply absent from the result — the same “this source contributes nothing” degrade discover_symbols already gives an empty themes list, never an exception.

Parameters:
  • symbols (Collection[str])

  • max_age (timedelta)

  • now (datetime)

Return type:

dict[str, AnalystOpinion]

history()[source]

Every row, oldest first — the full history a rank-persistence computation (issue #39) ranks over.

Unlike latest_for_symbols, this is not scoped to a symbol set or a freshness window: the rank-persistence screen’s whole point is comparing a symbol’s older readings against its newest one, so filtering to “fresh” here would defeat it. Bounded by how often trader scan-universe runs, not by index size, so a full-table read is fine for a report command; there is no unbounded growth path this needs to guard against yet.

Return type:

list[ConsensusHistoryRow]

class trader.persistence.analyst_consensus.ConsensusHistoryRow(symbol, fetched_at, strong_buy, recommendation_mean)[source]

Bases: object

One analyst_consensus_history row, reduced to what a rank-over-time read needs (issue #39) — deliberately not the full AnalystOpinion shape, since history() can return hundreds of rows and every extra field would be carried for nothing a caller here uses.

Parameters:
  • symbol (str)

  • fetched_at (datetime)

  • strong_buy (int)

  • recommendation_mean (float | None)

symbol: str
fetched_at: datetime
strong_buy: int
recommendation_mean: float | None