1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
"""Financial statements — Income Statement, Balance Sheet, Cash Flow."""
import numpy as np
import pandas as pd
import streamlit as st
from services.data_service import get_income_statement, get_balance_sheet, get_cash_flow
from utils.formatters import fmt_large
# Rows where a decline is actually good (e.g. costs, expenses)
_INVERSE_ROWS = {
"cost of revenue", "cost of goods sold", "operating expenses",
"selling general administrative", "research development",
"interest expense", "income tax expense", "total expenses",
"reconciled cost of revenue",
}
def _is_inverse(label: str) -> bool:
return label.strip().lower() in _INVERSE_ROWS
def _fmt_cell(value) -> str:
try:
v = float(value)
except (TypeError, ValueError):
return "—"
return fmt_large(v)
def _yoy_raw(current, previous):
"""Return raw float YoY % change, or None."""
try:
c, p = float(current), float(previous)
if p == 0:
return None
return (c - p) / abs(p) * 100
except (TypeError, ValueError):
return None
def _yoy_str(pct) -> str:
if pct is None:
return "—"
arrow = "▲" if pct >= 0 else "▼"
return f"{arrow} {abs(pct):.1f}%"
def _build_statement(df: pd.DataFrame):
"""
Returns (display_df, color_df).
display_df: formatted string DataFrame for rendering.
color_df: same shape, cells are 'green', 'red', or '' for styling.
"""
df = df.copy()
df.columns = [str(c)[:10] for c in df.columns]
cols = list(df.columns)
display = pd.DataFrame(index=df.index)
colors = pd.DataFrame(index=df.index)
for i, col in enumerate(cols):
display[col] = df[col].apply(_fmt_cell)
colors[col] = ""
if i + 1 < len(cols):
prev_col = cols[i + 1]
yoy_label = f"YoY {col[:4]}"
raw_yoy = df.apply(lambda row: _yoy_raw(row[col], row[prev_col]), axis=1)
display[yoy_label] = raw_yoy.apply(_yoy_str)
def cell_color(row_label, pct):
if pct is None:
return ""
inverse = _is_inverse(row_label)
positive_change = pct >= 0
good = positive_change if not inverse else not positive_change
return "green" if good else "red"
colors[yoy_label] = pd.Series(
[cell_color(idx, pct) for idx, pct in zip(df.index, raw_yoy)],
index=df.index,
)
return display, colors
def _style(display: pd.DataFrame, colors: pd.DataFrame):
GREEN_BG = "background-color: rgba(46, 204, 113, 0.18); color: #7ce3a1;"
RED_BG = "background-color: rgba(231, 76, 60, 0.18); color: #ff8a8a;"
def apply_colors(row):
return [
GREEN_BG if colors.loc[row.name, col] == "green"
else RED_BG if colors.loc[row.name, col] == "red"
else ""
for col in display.columns
]
return display.style.apply(apply_colors, axis=1)
def render_financials(ticker: str):
col1, col2 = st.columns([1, 3])
with col1:
freq = st.radio("Frequency", ["Annual", "Quarterly"], horizontal=False)
quarterly = freq == "Quarterly"
tab_income, tab_balance, tab_cashflow = st.tabs(
["Income Statement", "Balance Sheet", "Cash Flow"]
)
with tab_income:
df = get_income_statement(ticker, quarterly=quarterly)
if df.empty:
st.info("Income statement data unavailable.")
else:
display, colors = _build_statement(df)
st.dataframe(_style(display, colors), use_container_width=True)
st.download_button(
"Download CSV",
df.to_csv().encode(),
file_name=f"{ticker.upper()}_income_{'quarterly' if quarterly else 'annual'}.csv",
mime="text/csv",
key=f"dl_income_{ticker}_{quarterly}",
)
with tab_balance:
df = get_balance_sheet(ticker, quarterly=quarterly)
if df.empty:
st.info("Balance sheet data unavailable.")
else:
display, colors = _build_statement(df)
st.dataframe(_style(display, colors), use_container_width=True)
st.download_button(
"Download CSV",
df.to_csv().encode(),
file_name=f"{ticker.upper()}_balance_{'quarterly' if quarterly else 'annual'}.csv",
mime="text/csv",
key=f"dl_balance_{ticker}_{quarterly}",
)
with tab_cashflow:
df = get_cash_flow(ticker, quarterly=quarterly)
if df.empty:
st.info("Cash flow data unavailable.")
else:
display, colors = _build_statement(df)
st.dataframe(_style(display, colors), use_container_width=True)
st.download_button(
"Download CSV",
df.to_csv().encode(),
file_name=f"{ticker.upper()}_cashflow_{'quarterly' if quarterly else 'annual'}.csv",
mime="text/csv",
key=f"dl_cashflow_{ticker}_{quarterly}",
)
|