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.
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.
| 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% |
top10_* reshuffled rank without a membership change in 29 of 128 stable pairs.opds is absent from the first 136 snapshots: it was only
added to the collector on 2026-09-02.| 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.
top10_long at 14:37 and
leaving at 15:00 crossed a rank threshold; it did not trigger anything.news_trending is the only list where consecutive snapshots are near-independent — sample
it, don't diff it.danelfin_* and chart_patterns are effectively daily. Polling them every 15 min costs
nothing (cache hits) but yields one new observation per day.top10_* — half of all pairs changed a row's contents
while the ten names stayed put.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.