mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-12 18:18:03 +00:00
Complete migration from Streamlit to Next.js 14 App Router + FastAPI backend. Frontend (Next.js 14): - 10 pages: Overview, Research, Valuation, Technical, Markets, Earnings, News, Portfolio, Filings, Settings - Terminal Noir dark theme with custom Tailwind config - TradingView Lightweight Charts for candlestick/volume - Valuation: DCF, Sensitivity Matrix, Monte Carlo, Tornado, Reverse DCF - Financial Statements table with YoY growth badges and margin rows - SEC EDGAR inline filing viewer with section tabs - News split-view with iframe article embedding - Technical Analysis with RSI, MACD, Bollinger, Fibonacci, Moving Averages - Earnings beat/miss visualization - AI Copilot chat panel with Gemini integration Backend (FastAPI): - 13 routers: market_data, financials, valuation, technical, earnings, insider, edgar, news, portfolio, analysis, chat, estimates, fx - Services: DCF engine, Monte Carlo simulation, sensitivity analysis, risk metrics, SEC parser, technical indicators - yfinance + yahooquery data sources with fallback pattern - SQLite caching layer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
3.0 KiB
Python
70 lines
3.0 KiB
Python
"""
|
|
Portfolio sidebar widgets — earnings calendar, dividends, news.
|
|
Split from tab9_portfolio.py to stay under 300-line limit.
|
|
"""
|
|
import streamlit as st
|
|
from data.portfolio import (
|
|
get_earnings_calendar, get_dividend_schedule, get_portfolio_news,
|
|
)
|
|
|
|
|
|
def render_earnings(tickers: tuple):
|
|
"""Earnings calendar widget."""
|
|
st.markdown("#### Earnings Calendar")
|
|
events = get_earnings_calendar(tickers)
|
|
if not events:
|
|
st.caption("No upcoming earnings found.")
|
|
return
|
|
for ev in events[:8]:
|
|
d = ev["days_until"]
|
|
color = "#F87171" if d <= 3 else "#FBBF24" if d <= 7 else "#6B7280"
|
|
st.markdown(
|
|
f'<div style="display:flex;align-items:center;gap:10px;padding:6px 0;'
|
|
f'border-bottom:1px solid rgba(255,255,255,0.06);">'
|
|
f'<span style="background:{color};color:#fff;padding:2px 8px;border-radius:4px;'
|
|
f'font-size:0.75rem;font-weight:700;min-width:40px;text-align:center;">D-{d}</span>'
|
|
f'<div><span style="color:#F3F4F6;font-weight:600;">{ev["name"]}</span>'
|
|
f'<br><span style="color:#6B7280;font-size:0.75rem;">{ev["ticker"]} · {ev["date"]}</span></div>'
|
|
f'</div>', unsafe_allow_html=True
|
|
)
|
|
|
|
|
|
def render_dividends(tickers: tuple):
|
|
"""Dividend schedule widget."""
|
|
st.markdown("#### Dividend Schedule")
|
|
divs = get_dividend_schedule(tickers)
|
|
if not divs:
|
|
st.caption("No dividend data found.")
|
|
return
|
|
for d in divs[:8]:
|
|
st.markdown(
|
|
f'<div style="padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.06);">'
|
|
f'<span style="color:#F3F4F6;font-weight:600;">{d["name"]}</span>'
|
|
f' <span style="color:#6B7280;font-size:0.8rem;">{d["ticker"]}</span><br>'
|
|
f'<span style="color:#34D399;font-size:0.85rem;">Yield: {d["yield_pct"]}</span>'
|
|
f' · <span style="color:#9CA3AF;font-size:0.8rem;">Ex-Date: {d["ex_date"]}</span>'
|
|
f' · <span style="color:#9CA3AF;font-size:0.8rem;">Annual: {d["amount"]}</span>'
|
|
f'</div>', unsafe_allow_html=True
|
|
)
|
|
|
|
|
|
def render_news(tickers: tuple):
|
|
"""Recent news for portfolio stocks."""
|
|
st.markdown("#### Portfolio News")
|
|
news = get_portfolio_news(tickers, max_per_ticker=2)
|
|
if not news:
|
|
st.caption("No recent news.")
|
|
return
|
|
from datetime import datetime
|
|
for n in news[:10]:
|
|
ts = n.get("published", 0)
|
|
date_str = datetime.fromtimestamp(ts).strftime("%m/%d") if ts else ""
|
|
link = n.get("link", "#")
|
|
st.markdown(
|
|
f'<div style="padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.06);">'
|
|
f'<span style="color:#60A5FA;font-weight:600;font-size:0.8rem;">{n["ticker"]}</span>'
|
|
f' <span style="color:#6B7280;font-size:0.75rem;">{n.get("publisher","")} · {date_str}</span><br>'
|
|
f'<a href="{link}" target="_blank" style="color:#F3F4F6;text-decoration:none;font-size:0.85rem;">'
|
|
f'{n["title"]}</a></div>', unsafe_allow_html=True
|
|
)
|