aboutsummaryrefslogtreecommitdiff
path: root/components/filings.py
diff options
context:
space:
mode:
authorTyler <tyler@tylerhoang.xyz>2026-03-29 15:41:42 -0700
committerTyler <tyler@tylerhoang.xyz>2026-03-29 15:41:42 -0700
commit3e1324eff69e4b121f85223758b872c7fb5dc027 (patch)
treeb9e34a461460036c4aac1fdc7dab33763ef255ce /components/filings.py
parent4fdcb4ce0f00bc8f62d50ba5d352dd2fe01cd7e7 (diff)
Migrate insiders and filings from FMP to yfinance
FMP v3 insider-trading and sec_filings endpoints are legacy-gated. Switch to yfinance (t.insider_transactions, t.sec_filings) which provides the same data for free with no API key required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'components/filings.py')
-rw-r--r--components/filings.py86
1 files changed, 31 insertions, 55 deletions
diff --git a/components/filings.py b/components/filings.py
index d366a2b..a1e4417 100644
--- a/components/filings.py
+++ b/components/filings.py
@@ -1,8 +1,7 @@
"""SEC filings — recent 10-K, 10-Q, 8-K and other forms with direct links."""
-import pandas as pd
import streamlit as st
-from services.fmp_service import get_sec_filings
+from services.data_service import get_sec_filings
_FORM_DESCRIPTIONS = {
@@ -24,22 +23,19 @@ _FORM_COLORS = {
}
-def _describe_form(form_type: str) -> str:
- return _FORM_DESCRIPTIONS.get(form_type.strip().upper(), "")
-
-
def render_filings(ticker: str):
with st.spinner("Loading SEC filings…"):
- raw = get_sec_filings(ticker)
+ filings = get_sec_filings(ticker)
- if not raw:
- st.info("No SEC filing data available. Requires FMP API key.")
+ if not filings:
+ st.info("No SEC filing data available for this ticker.")
return
- # Collect unique form types for filter
- form_types = sorted({str(r.get("type") or r.get("form") or "").strip() for r in raw if r.get("type") or r.get("form")})
- filter_options = ["All"] + [t for t in ["10-K", "10-Q", "8-K"] if t in form_types] + \
- [t for t in form_types if t not in ("10-K", "10-Q", "8-K")]
+ # yfinance returns: date (datetime.date), type, title, edgarUrl, exhibits (dict)
+ form_types = sorted({str(f.get("type", "")).strip() for f in filings if f.get("type")})
+ priority = [t for t in ["10-K", "10-Q", "8-K"] if t in form_types]
+ other = [t for t in form_types if t not in ("10-K", "10-Q", "8-K")]
+ filter_options = ["All"] + priority + other
filter_col, _ = st.columns([1, 3])
with filter_col:
@@ -47,69 +43,49 @@ def render_filings(ticker: str):
# Summary counts
counts = {}
- for r in raw:
- ft = str(r.get("type") or r.get("form") or "Other").strip()
+ for f in filings:
+ ft = str(f.get("type", "Other")).strip()
counts[ft] = counts.get(ft, 0) + 1
- priority_forms = ["10-K", "10-Q", "8-K"]
- summary_forms = [f for f in priority_forms if f in counts]
- if summary_forms:
- cols = st.columns(len(summary_forms))
- for col, ft in zip(cols, summary_forms):
- col.metric(ft, counts[ft])
+ if priority:
+ cols = st.columns(len(priority))
+ for col, ft in zip(cols, priority):
+ col.metric(ft, counts.get(ft, 0))
st.write("")
- # Filter
- if selected_type == "All":
- filtered = raw
- else:
- filtered = [
- r for r in raw
- if str(r.get("type") or r.get("form") or "").strip() == selected_type
- ]
+ filtered = filings if selected_type == "All" else [
+ f for f in filings if str(f.get("type", "")).strip() == selected_type
+ ]
if not filtered:
st.info("No filings match the current filter.")
return
- # Build display table
- rows = []
- for item in filtered:
- form_type = str(item.get("type") or item.get("form") or "—").strip()
- date = str(item.get("filingDate") or item.get("date") or "")[:10]
- description = item.get("description") or _describe_form(form_type) or "—"
- url = item.get("link") or item.get("finalLink") or item.get("url") or ""
- rows.append({
- "Date": date,
- "Form": form_type,
- "Description": description,
- "Link": url,
- })
-
- df = pd.DataFrame(rows)
+ for item in filtered[:40]:
+ form = str(item.get("type", "—")).strip()
+ date = str(item.get("date", ""))[:10]
+ title = item.get("title") or _FORM_DESCRIPTIONS.get(form, "")
+ edgar_url = item.get("edgarUrl", "")
+ exhibits = item.get("exhibits") or {}
- # Render as clickable table — Streamlit doesn't natively support link columns,
- # so we render each row as a compact card for the most recent 30 entries.
- for _, row in df.head(30).iterrows():
- form = row["Form"]
color = _FORM_COLORS.get(form, "rgba(255,255,255,0.05)")
- date = row["Date"]
- desc = row["Description"]
- link = row["Link"]
with st.container():
left, right = st.columns([5, 1])
with left:
st.markdown(
f"<div style='background:{color};padding:6px 10px;border-radius:4px;margin-bottom:2px'>"
- f"<strong>{form}</strong> &nbsp;·&nbsp; <span style='color:#9aa0b0;font-size:0.82rem'>{date}</span><br>"
- f"<span style='font-size:0.85rem'>{desc}</span>"
+ f"<strong>{form}</strong> &nbsp;·&nbsp; "
+ f"<span style='color:#9aa0b0;font-size:0.82rem'>{date}</span><br>"
+ f"<span style='font-size:0.85rem'>{title}</span>"
f"</div>",
unsafe_allow_html=True,
)
with right:
- if link:
+ # Prefer the actual filing doc over the Yahoo index page
+ doc_url = exhibits.get(form) or edgar_url
+ if doc_url:
st.markdown(
- f"<div style='padding-top:8px'><a href='{link}' target='_blank'>🔗 View</a></div>",
+ f"<div style='padding-top:8px'><a href='{doc_url}' target='_blank'>🔗 View</a></div>",
unsafe_allow_html=True,
)