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
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))
|