Every year, US companies agree to pay out billions of dollars to compensate shareholders they allegedly misled — and a huge chunk of that money is never collected. In 2025 alone there were seventy-four securities class action settlements totaling $3.0 billion, per Cornerstone Research’s annual review, with a median settlement of $17.3 million (the highest since 1997). The checks are sitting there for anyone who held the stock during the “class period.” No lawsuit to file, no lawyer to hire, no legal risk to you: you are a passive class member by virtue of a purchase you already made.
So why doesn’t everyone collect? Two reasons, and both are an opening if you’re the kind of person who reads this blog.
First: most investors never hear about the settlements in time. When 40 mutual fund managers were sued by their own shareholders in 2005, the allegation was that the funds had failed to collect nearly $2 billion in settlement payouts the funds were entitled to (Wolf Popper, the firm behind the suits, publishes the details; an analysis of 200 custodial-bank RFPs it cites found less than 10% even mentioned securities class action claims filing). If billion-dollar funds with entire compliance departments miss these deadlines, your broker definitely isn’t watching for you.
Second: even investors who know the game rarely have a systematic early-warning system. Settlement notices arrive by mail weeks late, get lost, or get filtered as spam. The claims bar date passes. Money forfeited — by design, the unclaimed portion reverts to the defendants or gets distributed to everyone who did file, meaning filers literally harvest the forgetful.
This article gives you the system: a tested Python monitor that watches SEC EDGAR for the earliest public signal of a settlement — the 8-K announcing it — extracts the claims administrator and deadline-adjacent dates, and spits out a watchlist plus calendar reminders so you file before the bar date instead of reading about it two years later. It ran live, end to end, on this VPS the day this article was published. No API keys, no paid services, all public government data.
The mechanics, minus the lawyer-speak
When a company accused of securities fraud settles, the money doesn’t go out automatically. A claims administrator (Epiq, Angeion, Garden City, JND and a handful of others) runs the claims process. If you bought the stock during the class period and held through it, you’re eligible — but you must file a proof of claim form before the claims bar date. Miss it and your slice goes to everyone else who filed, or back to the company.
The timeline is long, which is exactly why the early signal matters. Industry estimates put the average securities settlement at a little over 4.5 years from initial complaint to actual payout (The Hedge Fund Journal, quoting a claims-filing specialist), and Cornerstone Research data shows large cases average about 3.5 years from filing to settlement announcement alone. That long pipeline is your friend: you have years to file after the settlement, but only months after the settlement notice to get your claim form in — and the notice is the thing people miss.
Why this is legitimate and not some grey-area hack: you are not suing anyone. You are not in any litigation. Filing a valid claim as a class member is a right attached to shares you actually owned. The “aggressive” edge is purely informational — you simply refuse to be the person who leaves claim money on the table. The grey-adjacent flavor comes from the fact that you’re competing against inertia itself.
Where the money is: the biggest pools come from the biggest frauds. Enron shareholders filed a $40 billion lawsuit and were ultimately compensated $7.2 billion (Wikipedia’s Enron scandal article, citing the court record). WorldCom’s underwriters and Citigroup paid out multi-billion settlements — Citigroup alone settled for $2.65 billion in 2004 (Wikipedia). But you don’t need to have held Enron in 2001. Every year dozens of mid-cap companies settle for tens or hundreds of millions, and their retail shareholders are the least likely to claim. A 2025 median settlement of $17.3 million split among a modest class of shareholders is real money per person for anyone who held through a 30–60% drawdown — which, if you trade at all, you’ve done.
The early signal: EDGAR 8-Ks
Here’s the trader-brain insight: every material settlement gets disclosed in an 8-K filing — usually weeks or months before the claims administrator even launches the settlement website. The 8-K typically attaches the settlement agreement, names the claims administrator, and mentions the process. EDGAR’s full-text search indexes these within a day of filing, and the SEC publishes everything free.
So the pipeline is:
- Query EDGAR full-text search for recent 8-Ks containing “class action settlement”.
- Map the filer’s CIK to a ticker using the SEC’s public company_tickers.json.
- Fetch each 8-K document, look for known claims administrator names and deadline-flavored language.
- Emit a CSV watchlist and an .ics calendar file with reminders ahead of candidate dates.
You then take each watchlist hit, find the eventual settlement website (the administrator’s domain appears in the later long-form notice), and file your claim with the trade confirmations your broker can export.
The code (tested — it ran live the day this was published)
Save as sca_pipeline.py. Standard library only. The only courtesy requirement is the SEC’s fair-use policy: identify your requests with a User-Agent and stay under ~10 requests/second — the script sleeps 1.2s between document fetches.
#!/usr/bin/env python3
"""
SCA Settlement Watch -- DIY securities class action settlement monitor.
Pipeline:
1. Pull SEC EDGAR full-text search hits for recent 8-Ks mentioning
"class action settlement" (these often contain the claims administrator
and claims-bar deadline).
2. Map CIK -> ticker via SEC's public company_tickers.json.
3. Fetch the top 8-K documents, extract claims administrator names and
deadline-ish dates.
4. Emit watchlist CSV + ICS calendar reminders so you never miss a bar date.
All data public (SEC EDGAR). No API key required.
Rate-limit note: SEC fair-use policy = max ~10 requests/sec; we sleep between calls.
"""
import csv
import json
import re
import sys
import time
import urllib.request
import urllib.parse
from datetime import datetime, timedelta, timezone
from pathlib import Path
UA = "SolvincSettlementWatch/1.0 (research; contact@example.com)"
OUT_DIR = Path(__file__).resolve().parent / "sca_out"
REQUEST_PAUSE = 1.2 # seconds between SEC hits -- stay well under 10 req/s
CLAIMS_ADMINS = [
"Epiq", "Angeion", "Garden City", "JND Legal", "JBS Corporate",
"Gilardi", "Rust Consulting", "A.B. Data", "Strategic Claims",
"Heffler", "KCC", "Kurtzman Carson", "Simpluris", "Computershare",
]
DATE_RE = re.compile(
r"(January|February|March|April|May|June|July|August|September|October|"
r"November|December)\s+(\d{1,2}),\s+(20\d{2})"
)
def http_get(url: str, retries: int = 3) -> bytes:
last_err = None
for attempt in range(retries):
try:
req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read()
except Exception as e: # noqa: BLE001
last_err = e
time.sleep(2 * (attempt + 1))
raise RuntimeError(f"GET failed after {retries} tries: {url} ({last_err})")
def load_ticker_map() -> dict:
"""CIK (str, zero-padded) -> (ticker, name). Public-domain SEC dataset."""
raw = http_get("https://www.sec.gov/files/company_tickers.json")
data = json.loads(raw)
out = {}
for row in data.values():
cik = str(row["cik_str"]).zfill(10)
out[cik] = (row["ticker"], row["title"])
return out
def search_settlement_8ks(days_back: int = 45, size: int = 25) -> list:
"""EDGAR full-text search: recent 8-Ks that mention a class action settlement."""
start = (datetime.now(timezone.utc) - timedelta(days=days_back)).strftime("%Y-%m-%d")
end = datetime.now(timezone.utc).strftime("%Y-%m-%d")
url = "https://efts.sec.gov/LATEST/search-index?q=" + urllib.parse.quote('"class action settlement"')
url += "&forms=8-K&dateRange=custom&startdt=" + start + "&enddt=" + end + "&size=" + str(size)
data = json.loads(http_get(url))
hits = []
for h in data.get("hits", {}).get("hits", []):
src = h.get("_source", {})
adsh = src.get("adsh", "") # e.g. 0001104659-24-123456
ciks = src.get("ciks") or []
file_date = src.get("file_date", "")
# _id looks like "<adsh>:<filename>"
fname = h.get("_id", "").split(":", 1)[1] if ":" in h.get("_id", "") else ""
hits.append({"adsh": adsh, "cik": ciks[0] if ciks else None,
"file_date": file_date, "filename": fname})
return hits
def doc_url(cik: str, adsh: str, filename: str) -> str:
return (f"https://www.sec.gov/Archives/edgar/data/{int(cik)}/"
f"{adsh.replace('-', '')}/{filename}")
def scan_document(url: str) -> dict:
"""Fetch 8-K text; look for claims administrator + candidate deadline dates."""
text = http_get(url).decode("utf-8", errors="ignore")
low = text.lower()
admin = next((a for a in CLAIMS_ADMINS if a.lower() in low), None)
has_deadline_language = any(
k in low for k in ("claims bar", "bar date", "claim form", "deadline",
"postmarked", "exclusion deadline", "objection deadline"))
dates = [f"{m.group(1)} {m.group(2)}, {m.group(3)}"
for m in DATE_RE.finditer(text)]
return {"admin": admin, "deadline_language": has_deadline_language,
"dates": dates[:6], "url": url}
def parse_date(s: str):
for fmt in ("%B %d, %Y",):
try:
return datetime.strptime(s, fmt)
except ValueError:
continue
return None
def write_outputs(rows: list):
OUT_DIR.mkdir(exist_ok=True)
csv_path = OUT_DIR / "watchlist.csv"
with open(csv_path, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["ticker", "company", "filed", "admin_detected",
"deadline_language", "candidate_dates", "8k_url"])
for r in rows:
w.writerow([r["ticker"], r["company"], r["filed"], r["admin"],
r["deadline_language"], " | ".join(r["dates"]), r["url"]])
# ICS reminders (one week before each future candidate date)
ics_path = OUT_DIR / "sca_reminders.ics"
lines = ["BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//SCA Watch//EN"]
n = 0
for r in rows:
for d in r["dates"]:
dt = parse_date(d)
if not dt or dt <= datetime.now():
continue
remind = dt - timedelta(days=7)
n += 1
lines += ["BEGIN:VEVENT",
f"UID:sca-{n}-{r['ticker']}@solvinc",
f"DTSTAMP:{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}",
f"DTSTART:{remind.strftime('%Y%m%d')}",
f"SUMMARY:[{r['ticker']}] check SCA claim steps (deadline {d})",
f"DESCRIPTION:Verify claims-bar date in 8-K: {r['url']}",
"END:VEVENT"]
lines.append("END:VCALENDAR")
ics_path.write_text("\r\n".join(lines))
return csv_path, ics_path, n
def main() -> int:
print("[1/4] loading ticker map ...")
tickers = load_ticker_map()
print(f" {len(tickers)} companies in SEC map")
print("[2/4] searching EDGAR FTS for recent 'class action settlement' 8-Ks ...")
hits = search_settlement_8ks()
print(f" {len(hits)} candidate filings")
if not hits:
print(" no hits right now (index lag or holiday week) -- exiting clean")
return 0
rows, errors = [], 0
for i, h in enumerate(hits):
if not (h["cik"] and h["adsh"] and h["filename"]):
continue
tk = tickers.get(str(h["cik"]).zfill(10), ("?", "?"))
if tk[0] == "?":
continue # subsidiary filer without a mapped ticker -- skip
try:
info = scan_document(doc_url(h["cik"], h["adsh"], h["filename"]))
except Exception as e: # noqa: BLE001
errors += 1
print(f" ! {tk[0]}: {e}")
if errors >= 3:
print(" too many fetch errors -- aborting scan (network?)")
return 1
continue
rows.append({"ticker": tk[0], "company": tk[1][:60], "filed": h["file_date"],
"admin": info["admin"], "deadline_language": info["deadline_language"],
"dates": info["dates"], "url": info["url"]})
time.sleep(REQUEST_PAUSE)
if len(rows) >= 12:
break
print(f"[3/4] scanned {len(rows)} relevant filings "
f"({sum(1 for r in rows if r['admin'])} with a known claims administrator)")
for r in rows:
flag = "ADMIN:" + str(r["admin"]) if r["admin"] else "admin n/a"
print(f" {r['ticker']:<6} {r['filed']} {flag:<20} {r['company'][:40]}")
print("[4/4] writing watchlist.csv + sca_reminders.ics ...")
csv_path, ics_path, n_events = write_outputs(rows)
print(f" {csv_path} ({len(rows)} rows)")
print(f" {ics_path} ({n_events} reminders)")
return 0
if __name__ == "__main__":
sys.exit(main())
Actual output from the test run on this VPS (2026-10-27, exit code 0):
[1/4] loading ticker map ...
8023 companies in SEC map
[2/4] searching EDGAR FTS for recent 'class action settlement' 8-Ks ...
2 candidate filings
[3/4] scanned 2 relevant filings (1 with a known claims administrator)
CNDT 2026-10-16 ADMIN:Epiq CONDUENT Inc
CNDT 2026-10-16 admin n/a CONDUENT Inc
[4/4] writing watchlist.csv + sca_reminders.ics ...
.../sca_out/watchlist.csv (2 rows)
.../sca_out/sca_reminders.ics (0 reminders)
Two things to be straight about: first, no mocked data anywhere — every byte in that output came from live SEC servers, because EDGAR is free and doesn’t need a key. Second, the hit count on any given day varies; some weeks the full-text search turns up zero fresh settlement 8-Ks and the script exits cleanly with an empty watchlist. The raw settlement agreement 8-K for Conduent (first row) is a perfect example of why the doc-scan step exists: the settlement agreement exhibit itself flagged Epiq as administrator while the cover 8-K didn’t.
From watchlist to money: filing the claim
The script gets you the early signal; the claim filing is manual but boring-simple:
Confirm you’re in the class. The 8-K names the class period. Cross-check against your broker’s trade history export — if you bought within the window and held, you’re in.
Find the settlement website. Once the administrator launches, the case gets a dedicated domain — the administrator’s name from the watchlist row plus the company name will find it via any search engine. The long-form notice (filed as another 8-K exhibit or published on the site) has the exact claims bar date, the class definition, and the eligibility rules. The candidate dates your script extracted are where to look first.
Size up whether it’s worth filing. The settlement amount divided by class size gives you a rough per-claim expectation. A $50 million settlement across a class of 500,000 claimants is very different from the same amount across 5,000 holders of a thinly traded microcap. Your script gives you the ticker and the filing; the settlement website gives you the fund size; your broker gives you your position size. Thirty seconds of arithmetic tells you whether the filing is worth ten minutes of your time.
File the proof of claim. Online form plus, for larger positions, a trade-confirmation upload. Your broker can generate these — every mainstream broker has a “transaction history export” that satisfies administrators.
Wait. Distributions happen after all claims are processed and any objections resolve — months to a couple of years. This is the least glamorous passive income in existence, which is precisely why the money sits unclaimed.
The honest math
Let’s not pretend this makes you rich on its own. Your recovery per share is a small fraction of your loss on the position — settlements compensate a slice of the alleged damages, and the slice varies enormously case by case depending on case strength, class size, and how much of the drop the court attributes to the fraud. I could not verify one single universal recovery percentage, so I won’t invent one. What the verified numbers do tell you: 74 settlements worth $3.0 billion flowed through the system in 2025 alone, and that is money distributed to the people who filed. On a $10,000 position that dropped 40% in a fraud-related drawdown, a realistic outcome for a single qualifying case is a three- to low-four-figure check, paid out one to three years later.
The strategy compounds when you treat it as a portfolio overlay: hold a diversified basket of US equities (which you probably do), run this monitor daily via cron (add one line: 0 6 * * * cd /path && python3 sca_pipeline.py >> sca.log 2>&1), and file every claim your holdings qualify for. Over years, a typical index-holder accumulates dozens of eligible claims. The $2 billion mutual-fund lawsuit exists precisely because even professionals skip this when it’s not systematized. Your edge is that you systematized it.
Stack it with the rest of the claim-collecting stack this site covers — and note the annual rhythm: with ~74 settlements per year averaging into the tens of millions each, the flow of new cases never stops. The unclaimed share of these funds is, functionally, a subsidy paid by the disorganized to whoever shows up.
Risks and where this bites you
- Claim denials for bad data. Claims get rejected for settle-date vs trade-date mismatches and CUSIP changes (corporate actions create new CUSIPs; if your new CUSIP isn’t in the class definition, a naive data export shows zero eligible transactions — this exact trap is documented in claims-filing literature from Wolf Popper and ISS-linked claims vendors). Always check the administrator’s eligibility rules before submitting.
- Deadlines are final. Miss the bar date and there is no appeal for class members. That’s the entire reason the .ics reminders exist — and why you should treat the script’s extracted dates as candidates to verify, not gospel. The regex grabs every “Month D, YYYY” in the document; the real bar date is whichever one the administrator’s official notice confirms.
- It’s slow. You will file claims this year and get paid in 2028. If you need money next month, this isn’t your strategy — run it in parallel with your actual income engines.
- Tax treatment varies. Recovery on a stock you held is usually a return of capital or capital-gains adjustment, but jurisdictions differ — worth 15 minutes with a tax reference for your situation.
- The SEC data is authoritative but the deadline extraction is heuristic. The script’s date extraction is intentionally loose — it flags candidates, not certainties. Always verify the bar date on the administrator’s official settlement site before relying on it. Trust the notice, not the regex.
Also worth saying: securities litigation is an industry with its own incentives, and plaintiff firms’ fee awards come out of settlement funds — a court once approved roughly $688 million in fees from the Enron settlement alone. That’s a debate for law reviews. For you as a class member, the fee structure changes nothing about your right to file: you never pay the lawyers directly, and your claim is net of nothing you control.
The meta-lesson is the one this site keeps hammering: the “passive income” that actually pays is rarely a product someone sells you. It’s a deadline everyone else forgets, plus a script that doesn’t forget.