How the screener lists actually change

Measured on the production VPS, not inferred from the code.

Window: 157 snapshots in screeners/, 2026-08-31 07:57 → 2026-09-02 15:25 UTC (~2.3 days). Snapshot cadence: min 2 min, median 18 min, mean 21, max 65 — the 15-min data-refresh thread plus jitter, not a fixed clock.

Headline answer

Individual symbols are added and removed. The list is never replaced.

Across ~1,400 consecutive snapshot pairs there was exactly one zero-overlap transition (news_trending, whose 15 slots genuinely can turn over completely). Every other change was a one-in/one-out swap or a small batch. Mean Jaccard overlap between consecutive snapshots runs 0.72–1.00. Sizes are pinned (top10_* is always exactly 10) so an add implies a matching drop.

Per-list churn

list snaps typical size distinct syms seen pairs where the set changed mean +add mean −del mean overlap full wipes median symbol lifetime pairs where any field changed
top10_long 157 10 33 18% 0.3 0.3 0.95 0 11 snaps (~3h) 53%
top10_short 157 10 34 19% 0.4 0.4 0.94 0 4 snaps (~1h) 51%
news_trending 157 15 264 79% 2.4 2.3 0.72 1 2 snaps (~35m) 81%
finviz_long 157 8 116 26% 1.0 1.1 0.86 0 5 snaps (~1.5h) 31%
finviz_short 157 9 110 26% 0.9 1.0 0.87 0 8 snaps (~2.5h) 33%
danelfin_long 157 67 118 3% 0.5 0.3 0.99 0 66 snaps (~20h) 3%
danelfin_short 157 21 42 4% 0.7 0.5 0.96 0 33 snaps (~10h) 5%
chart_patterns 132 26 65 5% 0.4 0.3 0.98 0 45 snaps (~13h) 5%
trendspider 157 0 0 0% 0 0 1.00 0 — 0%
opds 21 0–1 1 5% 0.0 0.1 0.95 0 10 snaps (~3h) 5%

Three distinct refresh regimes

regime lists driver what a consumer sees
Continuous, high churn news_trending 24h Benzinga stream, ranked live membership changes ~4 times/hour, day and night
Market-hours, moderate churn top10_long/short, finviz_long/short recomputed each refresh pass off live quotes 11–20 membership changes/day, 77–86% of them inside NYSE RTH (13:30–20:00 UTC) for top10_*
Daily, near-static danelfin_*, chart_patterns provider publishes once a day; 12h tool-cache TTL 0–5 changes/day, ~25% in RTH — they land on the provider's clock, not the market's

top10_long illustrates the pattern: 28 changes over the window, of which 22 were a single symbol swapping out, and the 6 larger ones (4–7 names at once) all landed within minutes of the 13:30–15:30 UTC open, when the underlying momentum ranking reshuffles on the day's first prints. Names also churn back and forth — GPOR/OKTA/PAGP traded the last slot six times in one afternoon, which is rank noise at the cut line rather than six real signals.

danelfin_long's only large moves were a 65→91 expansion at 15:27 on 09-01 (the provider's daily publish) and an 18-ETF disappearance at 03:33 on 09-02 that reversed itself an hour later at 04:38 — a provider-side gap, not a signal change.

Consequences

Reproducing

ssh root@31.70.81.85 "cd /var/www/vhosts/beyondmarkets.ai/app/screeners && python3 /tmp/churn.py"

screener_snapshot.py writes one file per refresh pass and prunes past SCREENER_SNAPSHOT_KEEP_DAYS (120). 13 MB on disk for the window above.