124 lines
3.8 KiB
Python
Executable File
124 lines
3.8 KiB
Python
Executable File
# pip install sec-api pandas python-dotenv
|
|
from sec_api import QueryApi
|
|
import pandas as pd
|
|
from smtplib import SMTP
|
|
from email.mime.text import MIMEText
|
|
from email.mime.multipart import MIMEMultipart
|
|
from dotenv import load_dotenv
|
|
import os
|
|
|
|
# Load environment variables
|
|
load_dotenv()
|
|
|
|
# Configuration from .env
|
|
EMAIL_SENDER = os.getenv('EMAIL_SENDER')
|
|
EMAIL_RECIPIENT = os.getenv('EMAIL_RECIPIENT')
|
|
EMAIL_PASSWORD = os.getenv('EMAIL_PASSWORD')
|
|
SMTP_SERVER = os.getenv('SMTP_SERVER', 'smtp.gmail.com')
|
|
SMTP_PORT = int(os.getenv('SMTP_PORT', 587))
|
|
SEC_API_KEY = os.getenv('SEC_API_KEY')
|
|
|
|
if not SEC_API_KEY:
|
|
raise ValueError("SEC_API_KEY not set in .env file")
|
|
|
|
# Initialize SEC API
|
|
query_api = QueryApi(api_key=SEC_API_KEY)
|
|
|
|
# Point72 CIK
|
|
CIK = '0001603466'
|
|
|
|
# Query for latest two 13F-HR filings
|
|
query = {
|
|
"query": f'cik:{CIK} formType:"13F-HR"',
|
|
"sort": [{"filedAt": {"order": "desc"}}],
|
|
"size": 2
|
|
}
|
|
response = query_api.get_filings(query)
|
|
filings = response.get('filings', [])
|
|
|
|
if len(filings) < 2:
|
|
raise Exception(f"Not enough filings found for comparison: {len(filings)} found")
|
|
|
|
latest = filings[0]
|
|
prev = filings[1]
|
|
|
|
# Fetch holdings
|
|
latest_holdings = latest.get('holdings', [])
|
|
prev_holdings = prev.get('holdings', [])
|
|
|
|
if not latest_holdings or not prev_holdings:
|
|
raise Exception("No holdings data found in filings")
|
|
|
|
# Convert holdings to DataFrame
|
|
latest_df = pd.DataFrame(latest_holdings)
|
|
prev_df = pd.DataFrame(prev_holdings)
|
|
|
|
# Extract share amount from shrsOrPrnAmt dictionary
|
|
def extract_shares(row):
|
|
if isinstance(row, dict):
|
|
return row.get('sshPrnamt', 0)
|
|
return row
|
|
|
|
if 'shrsOrPrnAmt' in latest_df.columns:
|
|
latest_df['shrsOrPrnAmt'] = latest_df['shrsOrPrnAmt'].apply(extract_shares)
|
|
prev_df['shrsOrPrnAmt'] = prev_df['shrsOrPrnAmt'].apply(extract_shares)
|
|
|
|
# Verify required columns
|
|
required_cols = ['cusip', 'nameOfIssuer', 'shrsOrPrnAmt', 'value']
|
|
for col in required_cols:
|
|
if col not in latest_df.columns or col not in prev_df.columns:
|
|
raise KeyError(f"Column {col} missing in holdings data")
|
|
|
|
# Set index for comparison
|
|
key_col = 'cusip'
|
|
latest_df = latest_df.set_index(key_col)
|
|
prev_df = prev_df.set_index(key_col)
|
|
|
|
# Additions
|
|
additions = latest_df[~latest_df.index.isin(prev_df.index)]
|
|
|
|
# Removals
|
|
removals = prev_df[~prev_df.index.isin(latest_df.index)]
|
|
|
|
# Changes >10%
|
|
both = latest_df.index.intersection(prev_df.index)
|
|
changes = latest_df.loc[both].join(prev_df.loc[both], lsuffix='_new', rsuffix='_old')
|
|
|
|
# Ensure share columns are numeric
|
|
changes['shrsOrPrnAmt_new'] = pd.to_numeric(changes['shrsOrPrnAmt_new'], errors='coerce')
|
|
changes['shrsOrPrnAmt_old'] = pd.to_numeric(changes['shrsOrPrnAmt_old'], errors='coerce')
|
|
|
|
# Calculate share change
|
|
changes['share_change'] = changes['shrsOrPrnAmt_new'] - changes['shrsOrPrnAmt_old']
|
|
changes = changes[changes['share_change'].notna()] # Remove rows with NaN changes
|
|
changes = changes[abs(changes['share_change']) / changes['shrsOrPrnAmt_old'].replace(0, 1) > 0.1]
|
|
|
|
# Summary
|
|
summary = f"""
|
|
Point72 13F Changes {prev['periodOfReport']} to {latest['periodOfReport']}
|
|
|
|
Additions ({len(additions)}):
|
|
{additions[['nameOfIssuer', 'shrsOrPrnAmt', 'value']].to_string() if not additions.empty else 'None'}
|
|
|
|
Removals ({len(removals)}):
|
|
{removals[['nameOfIssuer', 'shrsOrPrnAmt', 'value']].to_string() if not removals.empty else 'None'}
|
|
|
|
Changes ({len(changes)}):
|
|
{changes[['nameOfIssuer_new', 'share_change']].to_string() if not changes.empty else 'None'}
|
|
"""
|
|
|
|
# Email
|
|
msg = MIMEMultipart()
|
|
msg['From'] = EMAIL_SENDER
|
|
msg['To'] = EMAIL_RECIPIENT
|
|
msg['Subject'] = f'Point72 13F Update {latest["periodOfReport"]}'
|
|
msg.attach(MIMEText(summary, 'plain'))
|
|
|
|
server = SMTP(SMTP_SERVER, SMTP_PORT)
|
|
server.starttls()
|
|
server.login(EMAIL_SENDER, EMAIL_PASSWORD)
|
|
server.send_message(msg)
|
|
server.quit()
|
|
|
|
print("Email sent!")
|