Skip to content

KB/alert

Screener Exits Digest

Last verified

Auto-generated. This article is rebuilt from app/signals/config/alert_thresholds.json by scripts/build_alerts_kb.py. Edit the alert config and re-run the script β€” do not edit this file directly.

Screener Exits Digest

Alert ID: SCREENER_EXITS_DIGEST
Category: screener
Cross-detection: no

Weekly rollup of every name that left the screener program (ripeness-trust Wave 3, W3-3): pick-contract exits (horizon elapsed / price or regime invalidation), candidacy decay (relabeled tier_drop when a conviction downgrade was the proximate driver), and S2 evictions. ONE portfolio-level fire per week (8640min = 6d cooldown, not per-ticker), reusing GET /api/v1/screener/exits’ compute_exits_digest verbatim (no new grading). The cooldown is deliberately SHORTER than the 7d reporting window (screener_config.json screener_alerts.exits_digest_days, default 7): a cooldown set to exactly 7d is measured from the last fire against a screener that runs twice a trading day, so whether the weekly digest lands is decided by run-time jitter β€” a run drifting minutes early is suppressed and the digest slips a slot or a day. 6d spans every intra-week run and clears the 7-day cadence with margin, making it deterministically the first qualifying run of the week. The cost is that the 6d fire cadence overlaps the 7d lookback by a day, so a name exiting late in a window can appear in two consecutive digests β€” accepted: a duplicate line in an accountability rollup is cheaper than a skipped week. Skips silently when nothing exited in the window (no spam on a quiet week). Not calibration-gated (accountability surface, not a trade-entry signal).

Severity

INFO β€” positive signal or notable event; no action needed

Trigger condition

{
  "type": "computed",
  "computation": "screener_exits_digest"
}

Marker, not a rule the macro alert engine evaluates. app/signals/alerts.py:_eval_computed does not recognise screener_exits_digest, so the per-cycle evaluate_alerts loop returns False for it every cycle and this alert can never fire from the macro path.

The real trigger lives in app/signals/screener_alerts.py (emit_screener_alerts), which writes the alert row into the alerts table directly. It is fired from the screener run β€” emit_screener_alerts is a loud-not-fatal leg of run_screener (step 7, app/screener/pipeline.py). The definition stays in alert_thresholds.json so severity, message template, cooldown, the alerts_config admin surface, and this catalog all read from one place.

Message template

Screener: {name_count} name(s) exited the program ({count} exit event(s)) in the trailing {days}d ({since_date} β†’ {as_of_date}). Reasons: {reason_summary}. {never_ripened_count} never ripened, {unknown_count} unclassified.

Rendered by app/signals/screener_alerts.py (_render_message) via Python str.format over the per-event context it builds for the fire, NOT the macro signals dict. A placeholder that fails to resolve falls back to the RAW template β€” the same posture as the macro engine’s _format_message.

Cooldown / dedup

Notifies

See also