trader.persistence.watchlist module

Repository for the watchlist_events audit trail (requirements §7).

Audit only, never a worklist: nothing reads this table to decide what to scan next cycle — discover_symbols starts from discovery.themes fresh every cycle. This is what answers “why did it buy UUUU?” three weeks later, which is the whole reason Candidate carries headline and url in the first place.

WatchlistEvent has existed since Slice 1, with its columns fixed long before discovery existed: ticker, occurred_at, action, source, and a free-text note. There is no column for theme, origin, headline, or url, so record_discovery packs all four into note as JSON rather than silently dropping any of them — note is the only place left for the evidence, and the evidence is the point.

source is always SOURCE_CLAUDE_SCAN. The column’s own Slice-1 comment already names the split it exists for (“user_fixed” or “claude_scan”); this repository only ever writes the scan side; nothing here records an operator’s own edit to fixed_tickers.

trader.persistence.watchlist.ACTION_DISCOVERED = 'discovered'

The only action this repository ever writes. WatchlistEvent.action is shared with whatever future writer records a removal from the watchlist; this repository only ever adds.

trader.persistence.watchlist.SOURCE_CLAUDE_SCAN = 'claude_scan'

The only source this repository ever writes.

class trader.persistence.watchlist.WatchlistEventRepository(session_factory)[source]

Bases: object

Writes and reads the watchlist_events table.

Parameters:

session_factory (sessionmaker[Session])

record_discovery(*, symbol, theme, origin, headline, url, discovered_at)[source]

Record one accepted candidate. Returns the new row id.

theme, origin, headline, and url have no dedicated columns — see the module docstring — so they travel together as JSON in note.

Parameters:
  • symbol (str)

  • theme (str)

  • origin (str)

  • headline (str | None)

  • url (str | None)

  • discovered_at (datetime)

Return type:

int

recent(limit=20)[source]

The most recent events, newest first.

Parameters:

limit (int)

Return type:

list[WatchlistEvent]

discovered_symbols_since(cutoff)[source]

Distinct tickers record_discovery wrote at or after cutoff.

Read-only, for a report to describe “what discovery has recently surfaced” (issue #65’s sector-concentration measurement) — still not a worklist per the module docstring: nothing here decides what to scan next, and no caller may use this to choose a candidate. A symbol discovered under more than one theme in the window appears once, not once per event.

Parameters:

cutoff (datetime)

Return type:

list[str]