feat: Phase 3 FINRA bulk adapter + Phase 4 three-way merge with discrepancy flagging

Phase 3 — FINRA bulk short-interest ingest:
- Add finra_short_interest table to schema (per-symbol, per-settlement-date, per-exchange, with short/exempt/total volume, avg daily vol, days to cover)
- Create FinraIngestService: downloads FINRA consolidated pipe-delimited file from configurable base URL, parses Market|Symbol|ShortVolume|ShortExemptVolume|TotalVolume, stores in finra_short_interest table
- Create FinraBulkAdapter: SourceFetch that calls downloadAndIngestFinra, registers as finra-bulk source kind with finraShortinterest cache handler
- finraShortinterest handler reads latest settlement row per symbol from finra_short_interest table (no per-symbol kv_cache write; data is bulk-ingested)
- Register in index.ts adapter map + HANDLERS + del case

Phase 4 — three-way merge with discrepancy detection:
- shortInterest tRPC procedure now reads all 3 caches (yfinance, nasdaq, finra-bulk) in parallel
- Reconciliation hierarchy: FINRA (shares short) > NASDAQ > Yahoo
- daysToCover: NASDAQ (specific) > FINRA (computed) > Yahoo (short ratio fallback)
- settlementDate: FINRA > NASDAQ > Yahoo
- Discrepancy detection: compares sharesShort across available sources, flags >10% difference with discrepancyPct + discrepancyBetween
- Updated ShortInterestPanel: FINRA source badge, discrepancy warning banner, three-source disclaimer
- Updated trpc.ts client type for new shape
This commit is contained in:
Investor Flow Build
2026-07-25 13:03:49 -04:00
parent 76f60dc766
commit 3fa6b32916
8 changed files with 308 additions and 15 deletions
+18
View File
@@ -676,3 +676,21 @@ CREATE TABLE IF NOT EXISTS rotation_state (
sub_group TEXT, -- the strongest sub-group (e.g. "Financials")
checked_at TEXT
);
-- ===== Slice 22 — FINRA Short Interest (bulk ingest, twice-monthly) =====
CREATE TABLE IF NOT EXISTS finra_short_interest (
symbol TEXT NOT NULL,
settlement_date TEXT NOT NULL, -- YYYY-MM-DD of the short interest report
exchange TEXT, -- NASDAQ | NYSE | NYSE_AMERICAN | NYSE_ARCA | ALL
short_volume REAL, -- shares sold short
short_exempt REAL, -- short exempt volume
total_volume REAL, -- short + exempt
avg_daily_vol REAL, -- average daily volume (for days-to-cover)
days_to_cover REAL, -- computed: short_volume / avg_daily_vol
source_file TEXT, -- origin filename for audit
ingested_at TEXT NOT NULL,
PRIMARY KEY (symbol, settlement_date, exchange)
);
CREATE INDEX IF NOT EXISTS idx_finra_symbol ON finra_short_interest(symbol);
CREATE INDEX IF NOT EXISTS idx_finra_settlement ON finra_short_interest(settlement_date);