"""Build a point-in-time 13D/13G ownership event cache from SEC filings.""" from __future__ import annotations import argparse from pathlib import Path from typing import Any import pyarrow as pa import pyarrow.parquet as pq import requests from apps.backtester.run import _build_merged_snapshot_store, load_manifest, resolve_config from apps.tools.ownership_13d13g_probe import ( USER_AGENT, _build_events, _load_matched_filings, ) from libs.common.logging import configure_logging def _symbol_universe(config_path: str) -> set[str]: manifest = load_manifest(config_path) config = resolve_config(manifest) store = _build_merged_snapshot_store( manifest, config, snapshot_dir_override=None, ) return {str(symbol).upper() for symbol in store._bars.keys()} def _write_rows(path: Path, rows: list[dict[str, Any]]) -> None: path.parent.mkdir(parents=True, exist_ok=True) table = pa.Table.from_pylist(rows) pq.write_table(table, str(path)) def main() -> None: parser = argparse.ArgumentParser(description="Build PIT 13D/13G ownership cache from SEC filings") parser.add_argument("--config", action="append", required=True) parser.add_argument("--start", required=True, help="Start filing date YYYY-MM-DD") parser.add_argument("--end", required=True, help="End filing date YYYY-MM-DD") parser.add_argument("--output", default="data/reference/ownership_13d13g_events_pit.parquet") parser.add_argument("--cache-dir", default="data/cache/sec_13d13g") parser.add_argument("--include-13g-amendments", action="store_true") parser.add_argument("--user-agent", default=USER_AGENT) args = parser.parse_args() configure_logging("INFO") start_date = __import__("datetime").date.fromisoformat(args.start) end_date = __import__("datetime").date.fromisoformat(args.end) allowed_symbols: set[str] = set() representative_store = None for config_path in args.config: manifest = load_manifest(config_path) config = resolve_config(manifest) store = _build_merged_snapshot_store( manifest, config, snapshot_dir_override=None, ).slice_by_date_range(start_date, end_date) allowed_symbols.update(str(symbol).upper() for symbol in store._bars.keys()) if representative_store is None: representative_store = store if representative_store is None: raise SystemExit("No config store resolved.") session = requests.Session() session.headers.update({"User-Agent": args.user_agent}) # The probe helper matches only symbols available in the snapshot store. representative_store._bars = { symbol: representative_store._bars[symbol] for symbol in list(representative_store._bars.keys()) if str(symbol).upper() in allowed_symbols } matched_filings, form_counts = _load_matched_filings( store=representative_store, start_date=start_date, end_date=end_date, cache_dir=Path(args.cache_dir), include_13g_amendments=bool(args.include_13g_amendments), session=session, ) events, parse_stats = _build_events( records=matched_filings, cache_dir=Path(args.cache_dir), session=session, ) rows: list[dict[str, Any]] = [] for event in events: rows.append( { "symbol": event.symbol, "filing_date": event.filing_date.isoformat(), "as_of_date": event.filing_date.isoformat(), "form_type": event.form_type, "percent_owned": event.percent_owned, "aggregate_shares": event.aggregate_shares, "purpose_text": event.purpose_text, "purpose_housekeeping_flag": event.purpose_housekeeping_flag, "activist_flag": event.activist_flag, "is_amendment": event.is_amendment, "prior_percent_owned": event.prior_percent_owned, "percent_delta_points": event.percent_delta_points, "prior_form_group": event.prior_form_group, "is_initial_for_owner": event.is_initial_for_owner, "is_13g_to_13d_transition": event.is_13g_to_13d_transition, "ownership_strength_score": event.ownership_strength_score, "owner_name": event.owner_name, "owner_key": event.owner_key, "event_date": event.event_date.isoformat() if event.event_date else None, "source": "sec_13d13g_filing", } ) _write_rows(Path(args.output), rows) print( f"wrote {len(rows)} ownership events to {args.output} | " f"matched_filings={len(matched_filings)} | parsed={parse_stats.get('parsed', 0)} | " f"forms={form_counts}" ) if __name__ == "__main__": main()