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.
fithia2/apps/tools/build_ownership_13d13g_pit_...

130 lines
4.8 KiB
Python

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