From b54ac6d5b7e392bf424a7204fa2b89274ef7b666 Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Sun, 29 Mar 2026 17:29:24 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20universe=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=ED=92=88=EC=A7=88=20=EA=B0=9C=EC=84=A0=20=E2=80=94=20sanity?= =?UTF-8?q?=20check=20+=20preferred=20stock=20=ED=95=84=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SEC EDGAR fallback에서 "-" 포함 티커 제외 (preferred stock, BAC-PL 등) - W/R/Z suffix 티커 제외 (warrant, right, special) - build_snapshots: close > $1M 또는 shares < 100,000 또는 market_cap > $5T 제외 - screen_historical: market_cap > $5T 쿼리 레벨 필터 추가 Co-Authored-By: Claude Sonnet 4.6 --- app/services/universe_service.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/services/universe_service.py b/app/services/universe_service.py index e07b049..b3aa948 100644 --- a/app/services/universe_service.py +++ b/app/services/universe_service.py @@ -116,6 +116,11 @@ class UniverseService: # Keep only major US exchanges if exchange not in _US_EXCHANGES: continue + # Skip preferred stocks / warrants / rights / units (contain - or end in W/R/U/Z) + if "-" in ticker: + continue + if len(ticker) > 1 and ticker[-1] in ("W", "R", "Z") and ticker[:-1].isalpha(): + continue quotes.append({ "symbol": ticker, "shortName": str(row[name_idx]) if row[name_idx] else None, @@ -343,9 +348,14 @@ class UniverseService: shares = latest_shares if shares is None or close is None: continue + # Sanity checks: skip absurd values (data quality) + if close > 1_000_000 or close <= 0: # max BRK-A ~$600K + continue + if shares < 100_000: # too few shares for a real public co + continue market_cap = shares * close - if math.isnan(market_cap) or market_cap <= 0: + if math.isnan(market_cap) or market_cap <= 0 or market_cap > 5e12: continue # Normalize to first of month @@ -537,7 +547,12 @@ class UniverseService: snapshot_dt = datetime(target.year, target.month, 1, tzinfo=timezone.utc) - conditions = [UniverseSnapshot.snapshot_date == snapshot_dt] + # Always exclude clearly bad data (sanity cap: $5T max, historical record is ~$3.7T) + _MAX_MARKET_CAP = 5e12 + conditions = [ + UniverseSnapshot.snapshot_date == snapshot_dt, + UniverseSnapshot.market_cap <= _MAX_MARKET_CAP, + ] if market_cap_min is not None: conditions.append(UniverseSnapshot.market_cap >= market_cap_min) if market_cap_max is not None: