You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

71 lines
2.4 KiB
Python

"""
Audit Form 4 coverage for all universe_ticker_registry tickers.
Prints per-ticker earliest filing_date and P-code buy count.
Flags tickers with no data or earliest date after the baseline.
Usage (inside container):
python scripts/audit_form4_coverage.py [--baseline YYYY-MM-DD]
"""
import asyncio
import os
import sys
from datetime import date
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
async def audit(baseline: date) -> None:
from app.core.database import AsyncSessionLocal
from sqlalchemy import text
async with AsyncSessionLocal() as db:
rows = await db.execute(text("""
WITH u AS (SELECT ticker FROM universe_ticker_registry ORDER BY ticker),
e AS (
SELECT ticker,
MIN(filing_date)::date AS earliest,
COUNT(*) AS total_rows,
SUM(CASE WHEN transaction_code = 'P' AND shares > 0 THEN 1 ELSE 0 END) AS p_buys
FROM insider_transactions
GROUP BY ticker
)
SELECT u.ticker, e.earliest, e.total_rows, e.p_buys
FROM u LEFT JOIN e USING (ticker)
ORDER BY e.earliest NULLS FIRST, u.ticker
"""))
results = rows.fetchall()
no_data, late, ok = [], [], []
for ticker, earliest, total_rows, p_buys in results:
if earliest is None:
no_data.append(ticker)
elif earliest > baseline:
late.append((ticker, earliest, total_rows, p_buys))
else:
ok.append((ticker, earliest, total_rows, p_buys))
print(f"\n=== Form 4 Coverage Audit (baseline: {baseline}) ===")
print(f"Universe: {len(results)} tickers | OK: {len(ok)} | Late: {len(late)} | No data: {len(no_data)}\n")
if no_data:
print(f"NO DATA ({len(no_data)}): {', '.join(no_data)}\n")
if late:
print(f"LATE (earliest > {baseline}):")
for ticker, earliest, total_rows, p_buys in late:
print(f" {ticker:<8} earliest={earliest} rows={total_rows or 0} P-buys={p_buys or 0}")
print()
print(f"OK: {len(ok)} tickers meet {baseline} baseline")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--baseline", default="2024-04-01")
args = parser.parse_args()
baseline = date.fromisoformat(args.baseline)
asyncio.run(audit(baseline))