From 4fdcb4ce0f00bc8f62d50ba5d352dd2fe01cd7e7 Mon Sep 17 00:00:00 2001 From: Openclaw Date: Sun, 29 Mar 2026 13:21:39 -0700 Subject: Add historical ratios, forward estimates, insider transactions, SEC filings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - services/fmp_service.py: add get_historical_ratios, get_historical_key_metrics, get_analyst_estimates, get_insider_transactions, get_sec_filings - components/valuation.py: add Historical Ratios and Forward Estimates subtabs - components/insiders.py: new — insider buy/sell summary, monthly chart, detail table - components/filings.py: new — SEC filings with type filter and direct links - app.py: wire in Insiders and Filings top-level tabs --- components/filings.py | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 components/filings.py (limited to 'components/filings.py') diff --git a/components/filings.py b/components/filings.py new file mode 100644 index 0000000..d366a2b --- /dev/null +++ b/components/filings.py @@ -0,0 +1,115 @@ +"""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 + + +_FORM_DESCRIPTIONS = { + "10-K": "Annual report", + "10-Q": "Quarterly report", + "8-K": "Material event disclosure", + "DEF 14A": "Proxy statement", + "S-1": "IPO registration", + "S-3": "Securities registration", + "4": "Insider ownership change", + "SC 13G": "Beneficial ownership (passive)", + "SC 13D": "Beneficial ownership (active)", +} + +_FORM_COLORS = { + "10-K": "rgba(79,142,247,0.15)", + "10-Q": "rgba(130,224,170,0.15)", + "8-K": "rgba(247,162,79,0.15)", +} + + +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) + + if not raw: + st.info("No SEC filing data available. Requires FMP API key.") + 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")] + + filter_col, _ = st.columns([1, 3]) + with filter_col: + selected_type = st.selectbox("Form type", options=filter_options, index=0, key="filings_filter") + + # Summary counts + counts = {} + for r in raw: + ft = str(r.get("type") or r.get("form") or "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]) + 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 + ] + + 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) + + # 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"
" + f"{form}  ·  {date}
" + f"{desc}" + f"
", + unsafe_allow_html=True, + ) + with right: + if link: + st.markdown( + f"
🔗 View
", + unsafe_allow_html=True, + ) -- cgit v1.3-2-g0d8e