Dmitry Beresnev commited on
Commit
50e9fe0
ยท
1 Parent(s): 4a3e002

app refactoring

Browse files
Files changed (5) hide show
  1. app/charts.py +142 -0
  2. app/data.py +88 -0
  3. app/main.py +104 -730
  4. app/styles.py +331 -0
  5. app/ui.py +167 -0
app/charts.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Chart creation utilities for the financial dashboard."""
2
+
3
+ import plotly.graph_objects as go
4
+ import pandas as pd
5
+
6
+
7
+ def get_dark_theme_layout():
8
+ """Get common dark theme layout settings for all charts."""
9
+ return dict(
10
+ plot_bgcolor="#0d1117",
11
+ paper_bgcolor="#0e1117",
12
+ font=dict(color="#e6edf3", size=12, family="Arial, sans-serif"),
13
+ xaxis=dict(
14
+ gridcolor="#30363d",
15
+ showgrid=True,
16
+ zeroline=False,
17
+ color="#8b949e"
18
+ ),
19
+ yaxis=dict(
20
+ gridcolor="#30363d",
21
+ showgrid=True,
22
+ zeroline=False,
23
+ color="#8b949e"
24
+ ),
25
+ legend=dict(
26
+ bgcolor="rgba(13, 17, 23, 0.8)",
27
+ bordercolor="#30363d",
28
+ borderwidth=1,
29
+ font=dict(color="#e6edf3")
30
+ ),
31
+ hoverlabel=dict(
32
+ bgcolor="#0d1117",
33
+ bordercolor="#30363d",
34
+ font=dict(color="#e6edf3")
35
+ )
36
+ )
37
+
38
+
39
+ def create_price_chart(df: pd.DataFrame, symbol: str, period: int) -> go.Figure:
40
+ """Create price chart with SMA and EMA indicators."""
41
+ fig = go.Figure()
42
+
43
+ fig.add_trace(go.Scatter(
44
+ x=df.index, y=df["close"],
45
+ name="Close Price",
46
+ line=dict(color="#0066ff", width=2.5)
47
+ ))
48
+ fig.add_trace(go.Scatter(
49
+ x=df.index, y=df["SMA"],
50
+ name=f"SMA {period}",
51
+ line=dict(color="#00d084", width=2, dash="dash")
52
+ ))
53
+ fig.add_trace(go.Scatter(
54
+ x=df.index, y=df["EMA"],
55
+ name=f"EMA {period}",
56
+ line=dict(color="#ffa500", width=2, dash="dot")
57
+ ))
58
+
59
+ layout = get_dark_theme_layout()
60
+ fig.update_layout(
61
+ title=f"{symbol} - Price with Moving Averages",
62
+ xaxis_title="Date",
63
+ yaxis_title="Price ($)",
64
+ hovermode="x unified",
65
+ template="plotly_dark",
66
+ height=500,
67
+ margin=dict(l=0, r=0, t=40, b=0),
68
+ **layout
69
+ )
70
+
71
+ return fig
72
+
73
+
74
+ def create_rsi_chart(df: pd.DataFrame, symbol: str) -> go.Figure:
75
+ """Create RSI (Relative Strength Index) chart."""
76
+ fig = go.Figure()
77
+
78
+ fig.add_trace(go.Scatter(
79
+ x=df.index, y=df["RSI"],
80
+ name="RSI",
81
+ line=dict(color="#ff3838", width=2.5),
82
+ fill="tozeroy",
83
+ fillcolor="rgba(255, 56, 56, 0.15)"
84
+ ))
85
+
86
+ fig.add_hline(y=70, line_dash="dash", line_color="rgba(255, 165, 0, 0.6)",
87
+ annotation_text="Overbought (70)")
88
+ fig.add_hline(y=30, line_dash="dash", line_color="rgba(0, 208, 132, 0.6)",
89
+ annotation_text="Oversold (30)")
90
+ fig.add_hline(y=50, line_dash="dot", line_color="rgba(139, 148, 158, 0.3)")
91
+
92
+ layout = get_dark_theme_layout()
93
+ layout["yaxis"]["range"] = [0, 100]
94
+
95
+ fig.update_layout(
96
+ title=f"{symbol} - Relative Strength Index (RSI)",
97
+ xaxis_title="Date",
98
+ yaxis_title="RSI",
99
+ hovermode="x unified",
100
+ template="plotly_dark",
101
+ height=500,
102
+ margin=dict(l=0, r=0, t=40, b=0),
103
+ **layout
104
+ )
105
+
106
+ return fig
107
+
108
+
109
+ def create_financial_chart(income_data: pd.DataFrame) -> go.Figure:
110
+ """Create financial revenue and net income chart."""
111
+ fig = go.Figure()
112
+
113
+ fig.add_trace(go.Bar(
114
+ x=income_data['period_ending'],
115
+ y=income_data['total_revenue'],
116
+ name="Total Revenue",
117
+ marker=dict(color='#0066ff', opacity=0.9),
118
+ yaxis='y1'
119
+ ))
120
+
121
+ fig.add_trace(go.Bar(
122
+ x=income_data['period_ending'],
123
+ y=income_data['net_income'],
124
+ name="Net Income",
125
+ marker=dict(color='#00d084', opacity=0.9),
126
+ yaxis='y1'
127
+ ))
128
+
129
+ layout = get_dark_theme_layout()
130
+ fig.update_layout(
131
+ title="Revenue & Net Income (Annual)",
132
+ xaxis_title="Period",
133
+ yaxis_title="Amount ($)",
134
+ hovermode="x unified",
135
+ template="plotly_dark",
136
+ height=400,
137
+ barmode='group',
138
+ margin=dict(l=0, r=0, t=40, b=0),
139
+ **layout
140
+ )
141
+
142
+ return fig
app/data.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Data fetching and processing utilities for the financial dashboard."""
2
+
3
+ import pandas as pd
4
+ from openbb import sdk
5
+
6
+
7
+ def load_stock_data(symbol: str) -> pd.DataFrame:
8
+ """Load historical stock price data."""
9
+ df = sdk.equity.price.historical(symbol=symbol).to_dataframe()
10
+ return df
11
+
12
+
13
+ def load_company_profile(symbol: str):
14
+ """Load company profile information."""
15
+ profile_response = sdk.equity.profile(symbol=symbol)
16
+ profile_info = profile_response.results[0] if hasattr(profile_response, 'results') and profile_response.results else None
17
+ return profile_info
18
+
19
+
20
+ def load_income_statement(symbol: str) -> pd.DataFrame:
21
+ """Load company income statement data."""
22
+ income_stmt = sdk.equity.fundamental.income(symbol=symbol).to_dataframe()
23
+ return income_stmt
24
+
25
+
26
+ def calculate_technical_indicators(df: pd.DataFrame, period: int) -> pd.DataFrame:
27
+ """Calculate SMA, EMA, and RSI indicators."""
28
+ df["SMA"] = df["close"].rolling(period).mean()
29
+ df["EMA"] = df["close"].ewm(span=period, adjust=False).mean()
30
+
31
+ # Calculate RSI
32
+ delta = df["close"].diff()
33
+ gain = delta.clip(lower=0)
34
+ loss = -1 * delta.clip(upper=0)
35
+ avg_gain = gain.rolling(period).mean()
36
+ avg_loss = loss.rolling(period).mean()
37
+ rs = avg_gain / avg_loss
38
+ df["RSI"] = 100 - (100 / (1 + rs))
39
+
40
+ return df
41
+
42
+
43
+ def format_financial_value(value) -> str:
44
+ """Format financial values with appropriate units."""
45
+ if pd.isna(value):
46
+ return "N/A"
47
+ if abs(value) >= 1e9:
48
+ return f"${value/1e9:.2f}B"
49
+ elif abs(value) >= 1e6:
50
+ return f"${value/1e6:.2f}M"
51
+ else:
52
+ return f"${value:.2f}"
53
+
54
+
55
+ def get_price_metrics(df: pd.DataFrame) -> dict:
56
+ """Calculate key price metrics."""
57
+ current_price = df["close"].iloc[-1]
58
+ prev_close = df["close"].iloc[-2] if len(df) > 1 else df["close"].iloc[0]
59
+ price_change = current_price - prev_close
60
+ price_change_pct = (price_change / prev_close) * 100 if prev_close != 0 else 0
61
+
62
+ return {
63
+ "current_price": current_price,
64
+ "price_change": price_change,
65
+ "price_change_pct": price_change_pct,
66
+ "high_52w": df['high'].max(),
67
+ "low_52w": df['low'].min(),
68
+ }
69
+
70
+
71
+ def get_profitability_metrics(income_data: pd.Series) -> dict:
72
+ """Calculate profitability metrics from income statement."""
73
+ total_rev = income_data.get('total_revenue', 0)
74
+ gross_prof = income_data.get('gross_profit', 0)
75
+ net_inc = income_data.get('net_income', 0)
76
+ operating_inc = income_data.get('operating_income', 0)
77
+
78
+ metrics = {}
79
+
80
+ if total_rev and total_rev > 0:
81
+ metrics["gross_margin"] = (gross_prof / total_rev) * 100 if pd.notna(gross_prof) else 0
82
+ metrics["net_margin"] = (net_inc / total_rev) * 100 if pd.notna(net_inc) else 0
83
+ if operating_inc:
84
+ metrics["operating_margin"] = (operating_inc / total_rev) * 100
85
+ else:
86
+ metrics = {"gross_margin": 0, "net_margin": 0}
87
+
88
+ return metrics
app/main.py CHANGED
@@ -1,15 +1,35 @@
 
 
1
  import streamlit as st
2
- import pandas as pd
3
- import plotly.graph_objects as go
4
- from openbb import sdk
5
  from dotenv import load_dotenv
6
  import os
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
 
9
  load_dotenv()
10
  token = os.getenv("TOKEN")
11
 
12
- # ---- Page Configuration ----
13
  st.set_page_config(
14
  page_title="Financial Dashboard",
15
  page_icon="๐Ÿ“ˆ",
@@ -20,336 +40,8 @@ st.set_page_config(
20
  }
21
  )
22
 
23
- # ---- Custom CSS for Dark Theme ----
24
- st.markdown("""
25
- <style>
26
- :root {
27
- --primary-color: #0066ff;
28
- --secondary-color: #1f77e2;
29
- --success-color: #00d084;
30
- --danger-color: #ff3838;
31
- --warning-color: #ffa500;
32
- --bg-dark: #0e1117;
33
- --bg-darker: #010409;
34
- --text-primary: #e6edf3;
35
- --text-secondary: #8b949e;
36
- --border-color: #30363d;
37
- }
38
-
39
- /* Main background */
40
- html, body {
41
- background-color: var(--bg-darker) !important;
42
- color: var(--text-primary) !important;
43
- margin: 0 !important;
44
- padding: 0 !important;
45
- }
46
-
47
- /* Streamlit containers */
48
- .main, [data-testid="stAppViewContainer"] {
49
- background-color: var(--bg-dark) !important;
50
- }
51
-
52
- /* Hide header and footer */
53
- [data-testid="stHeader"] {
54
- background-color: var(--bg-dark) !important;
55
- }
56
-
57
- [data-testid="stToolbar"] {
58
- background-color: var(--bg-dark) !important;
59
- }
60
-
61
- .stApp {
62
- background-color: var(--bg-dark) !important;
63
- }
64
-
65
- [data-testid="stDecoration"] {
66
- background-color: var(--bg-dark) !important;
67
- }
68
-
69
- [data-testid="stSidebar"] {
70
- background-color: #0d1117 !important;
71
- border-right: 1px solid var(--border-color);
72
- }
73
-
74
- /* Text colors */
75
- p, span, div, h1, h2, h3, h4, h5, h6, label, li, a {
76
- color: var(--text-primary) !important;
77
- }
78
-
79
- /* Headings */
80
- h1, h2, h3 {
81
- color: var(--text-primary) !important;
82
- font-weight: 700 !important;
83
- }
84
-
85
- /* Links */
86
- a {
87
- color: var(--primary-color) !important;
88
- text-decoration: none !important;
89
- }
90
-
91
- a:hover {
92
- color: var(--secondary-color) !important;
93
- text-decoration: underline !important;
94
- }
95
-
96
- /* Labels and text inputs */
97
- label {
98
- color: var(--text-primary) !important;
99
- font-weight: 500 !important;
100
- }
101
-
102
- /* Paragraph text */
103
- p {
104
- color: var(--text-primary) !important;
105
- line-height: 1.6 !important;
106
- }
107
-
108
- /* Metric card styling */
109
- [data-testid="metric-container"] {
110
- background: linear-gradient(135deg, #1f2937 0%, #111827 100%) !important;
111
- border: 1px solid var(--border-color) !important;
112
- border-radius: 10px !important;
113
- padding: 1.5rem !important;
114
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3) !important;
115
- }
116
-
117
- .metric-card {
118
- background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
119
- padding: 1.5rem;
120
- border-radius: 10px;
121
- border: 1px solid var(--border-color);
122
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
123
- }
124
-
125
- .metric-value {
126
- font-size: 2.5rem;
127
- font-weight: 700;
128
- color: var(--primary-color);
129
- margin: 0.5rem 0;
130
- }
131
-
132
- .metric-label {
133
- font-size: 0.875rem;
134
- color: var(--text-secondary);
135
- text-transform: uppercase;
136
- letter-spacing: 0.05em;
137
- }
138
-
139
- .section-title {
140
- color: var(--text-primary);
141
- border-bottom: 2px solid var(--primary-color);
142
- padding-bottom: 1rem;
143
- margin-top: 2rem;
144
- margin-bottom: 1.5rem;
145
- }
146
-
147
- /* Button styling */
148
- .stButton > button {
149
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important;
150
- color: #ffffff !important;
151
- border: none !important;
152
- border-radius: 8px !important;
153
- padding: 0.75rem 2rem !important;
154
- font-weight: 700 !important;
155
- transition: all 0.3s ease !important;
156
- box-shadow: 0 4px 6px rgba(0, 102, 255, 0.2) !important;
157
- }
158
-
159
- .stButton > button:hover {
160
- box-shadow: 0 8px 16px rgba(0, 102, 255, 0.4) !important;
161
- transform: translateY(-2px) !important;
162
- }
163
-
164
- .stButton > button:active {
165
- transform: translateY(0) !important;
166
- }
167
-
168
- /* Input fields */
169
- [data-testid="stTextInput"] input,
170
- [data-testid="stSlider"] input {
171
- background-color: #161b22 !important;
172
- border: 1px solid var(--border-color) !important;
173
- color: var(--text-primary) !important;
174
- border-radius: 6px !important;
175
- }
176
-
177
- [data-testid="stTextInput"] input::placeholder {
178
- color: var(--text-secondary) !important;
179
- }
180
-
181
- /* Slider */
182
- [data-testid="stSlider"] {
183
- color: var(--primary-color) !important;
184
- }
185
-
186
- /* Tabs */
187
- [data-testid="stTabs"] [role="tablist"] {
188
- background-color: transparent !important;
189
- border-bottom: 2px solid var(--border-color) !important;
190
- }
191
-
192
- [data-testid="stTabs"] [role="tab"] {
193
- color: var(--text-secondary) !important;
194
- background-color: transparent !important;
195
- border: none !important;
196
- padding: 1rem 1.5rem !important;
197
- }
198
-
199
- [data-testid="stTabs"] [role="tab"][aria-selected="true"] {
200
- color: var(--primary-color) !important;
201
- border-bottom: 3px solid var(--primary-color) !important;
202
- }
203
-
204
- /* Dataframe */
205
- [data-testid="dataframe"] {
206
- background-color: #0d1117 !important;
207
- }
208
-
209
- .dataframe {
210
- background-color: #0d1117 !important;
211
- color: var(--text-primary) !important;
212
- }
213
-
214
- /* Info/Error boxes */
215
- [data-testid="stInfo"],
216
- [data-testid="stSuccess"],
217
- [data-testid="stWarning"],
218
- [data-testid="stError"] {
219
- background-color: rgba(0, 102, 255, 0.1) !important;
220
- border-left: 4px solid var(--primary-color) !important;
221
- border-radius: 6px !important;
222
- }
223
-
224
- [data-testid="stError"] {
225
- background-color: rgba(255, 56, 56, 0.1) !important;
226
- border-left-color: var(--danger-color) !important;
227
- }
228
-
229
- /* Markdown */
230
- [data-testid="stMarkdown"] {
231
- color: var(--text-primary) !important;
232
- }
233
-
234
- /* Expander */
235
- [data-testid="stExpander"] {
236
- background-color: #161b22 !important;
237
- border: 1px solid var(--border-color) !important;
238
- border-radius: 6px !important;
239
- }
240
-
241
- /* Metric text styling */
242
- [data-testid="metric-container"] p {
243
- color: var(--text-primary) !important;
244
- }
245
-
246
- [data-testid="metric-container"] [data-testid="stMetricValue"] {
247
- color: var(--primary-color) !important;
248
- font-weight: 700 !important;
249
- }
250
-
251
- /* Slider label color */
252
- [data-testid="stSlider"] label {
253
- color: var(--text-primary) !important;
254
- }
255
-
256
- /* Text input label */
257
- [data-testid="stTextInput"] label {
258
- color: var(--text-primary) !important;
259
- }
260
-
261
- /* Write and markdown text */
262
- [data-testid="stMarkdownContainer"] p {
263
- color: var(--text-primary) !important;
264
- }
265
-
266
- [data-testid="stMarkdownContainer"] strong {
267
- color: var(--primary-color) !important;
268
- font-weight: 600 !important;
269
- }
270
-
271
- /* Spinner text */
272
- [data-testid="stSpinner"] {
273
- color: var(--primary-color) !important;
274
- }
275
-
276
- /* Column separators */
277
- hr {
278
- border-color: var(--border-color) !important;
279
- }
280
-
281
- /* Scrollbar */
282
- ::-webkit-scrollbar {
283
- width: 8px;
284
- height: 8px;
285
- }
286
-
287
- ::-webkit-scrollbar-track {
288
- background: #0d1117;
289
- }
290
-
291
- ::-webkit-scrollbar-thumb {
292
- background: var(--border-color);
293
- border-radius: 4px;
294
- }
295
-
296
- ::-webkit-scrollbar-thumb:hover {
297
- background: var(--primary-color);
298
- }
299
-
300
- /* Selection highlighting */
301
- ::selection {
302
- background-color: var(--primary-color);
303
- color: #fff;
304
- }
305
-
306
- /* Fix all white backgrounds */
307
- .stApp > header {
308
- background-color: var(--bg-dark) !important;
309
- }
310
-
311
- .stApp > header::before {
312
- background: none !important;
313
- }
314
-
315
- .stApp > header::after {
316
- background: none !important;
317
- }
318
-
319
- /* Streamlit elements background */
320
- [data-testid="stVerticalBlock"] {
321
- background-color: transparent !important;
322
- }
323
-
324
- [data-testid="stVerticalBlockBorderWrapper"] {
325
- background-color: transparent !important;
326
- }
327
-
328
- /* Remove white decorative elements */
329
- .st-emotion-cache-1gvbgyg {
330
- background-color: var(--bg-dark) !important;
331
- }
332
-
333
- .st-emotion-cache-1jicfl2 {
334
- background-color: var(--bg-dark) !important;
335
- }
336
-
337
- /* Ensure all root divs are dark */
338
- div[class*="st-"] {
339
- background-color: transparent !important;
340
- }
341
-
342
- /* Modal and overlay backgrounds */
343
- .stModal {
344
- background-color: var(--bg-dark) !important;
345
- }
346
-
347
- /* Alert boxes background */
348
- .stAlert {
349
- background-color: rgba(0, 102, 255, 0.1) !important;
350
- }
351
- </style>
352
- """, unsafe_allow_html=True)
353
 
354
  # ---- Header ----
355
  st.markdown("# ๐Ÿ“ˆ Financial Analysis Dashboard")
@@ -365,410 +57,92 @@ with st.sidebar:
365
  st.markdown("### About")
366
  st.info("This dashboard provides real-time technical analysis with comprehensive financial metrics.")
367
 
368
- if st.button("๐Ÿ“Š Load Dashboard", key="load_btn", use_container_width=True):
369
-
370
- try:
371
- # Load free stock data
372
- with st.spinner("Loading data..."):
373
- df = sdk.equity.price.historical(symbol=symbol).to_dataframe()
374
-
375
- # Load company profile
376
- profile_response = sdk.equity.profile(symbol=symbol)
377
- profile_info = profile_response.results[0] if hasattr(profile_response, 'results') and profile_response.results else None
378
-
379
- # Load income statement
380
- income_stmt = sdk.equity.fundamental.income(symbol=symbol).to_dataframe()
381
-
382
- # ---- Technical Indicators ----
383
- df["SMA"] = df["close"].rolling(period).mean()
384
- df["EMA"] = df["close"].ewm(span=period, adjust=False).mean()
385
- delta = df["close"].diff()
386
- gain = delta.clip(lower=0)
387
- loss = -1 * delta.clip(upper=0)
388
- avg_gain = gain.rolling(period).mean()
389
- avg_loss = loss.rolling(period).mean()
390
- rs = avg_gain / avg_loss
391
- df["RSI"] = 100 - (100 / (1 + rs))
392
-
393
- # ---- Display Key Metrics ----
394
- st.markdown('<div class="section-title">๐Ÿ“Š Price Metrics</div>', unsafe_allow_html=True)
395
-
396
- col1, col2, col3, col4 = st.columns(4)
397
-
398
- current_price = df["close"].iloc[-1]
399
- prev_close = df["close"].iloc[-2] if len(df) > 1 else df["close"].iloc[0]
400
- price_change = current_price - prev_close
401
- price_change_pct = (price_change / prev_close) * 100 if prev_close != 0 else 0
402
-
403
- with col1:
404
- st.metric("Current Price", f"${current_price:.2f}", f"{price_change:+.2f}", delta_color="normal")
405
-
406
- with col2:
407
- st.metric("Day Change %", f"{price_change_pct:+.2f}%", None, delta_color="normal")
408
 
409
- with col3:
410
- st.metric("52W High", f"${df['high'].max():.2f}")
 
 
 
 
 
 
 
411
 
412
- with col4:
413
- st.metric("52W Low", f"${df['low'].min():.2f}")
414
 
415
- # ---- Company Information & Financials ----
416
- st.markdown('<div class="section-title">๐Ÿ“‹ Company Information</div>', unsafe_allow_html=True)
 
417
 
418
- if profile_info:
419
- info_col1, info_col2 = st.columns(2)
420
- with info_col1:
421
- st.write(f"**Company Name:** {getattr(profile_info, 'name', 'N/A')}")
422
- st.write(f"**Sector:** {getattr(profile_info, 'sector', 'N/A')}")
423
- st.write(f"**Industry:** {getattr(profile_info, 'industry', 'N/A')}")
424
 
425
- with info_col2:
426
- st.write(f"**Country:** {getattr(profile_info, 'country', 'N/A')}")
427
- st.write(f"**Exchange:** {getattr(profile_info, 'exchange', 'N/A')}")
428
- st.write(f"**Website:** {getattr(profile_info, 'website', 'N/A')}")
429
-
430
- # ---- Financial Metrics ----
431
- if not income_stmt.empty:
432
- st.markdown('<div class="section-title">๐Ÿ’ฐ Financial Metrics</div>', unsafe_allow_html=True)
433
-
434
- # Get latest financial data
435
- latest_income = income_stmt.iloc[0] if len(income_stmt) > 0 else None
436
-
437
- if latest_income is not None:
438
- fin_col1, fin_col2, fin_col3, fin_col4 = st.columns(4)
439
-
440
- with fin_col1:
441
- revenue = latest_income.get('total_revenue', 0)
442
- if pd.notna(revenue) and revenue > 0:
443
- st.metric("Total Revenue", f"${revenue/1e9:.2f}B" if revenue > 1e9 else f"${revenue/1e6:.2f}M")
444
- else:
445
- st.metric("Total Revenue", "N/A")
446
-
447
- with fin_col2:
448
- net_income = latest_income.get('net_income', 0)
449
- if pd.notna(net_income) and net_income > 0:
450
- st.metric("Net Income", f"${net_income/1e9:.2f}B" if net_income > 1e9 else f"${net_income/1e6:.2f}M")
451
- else:
452
- st.metric("Net Income", "N/A")
453
-
454
- with fin_col3:
455
- gross_profit = latest_income.get('gross_profit', 0)
456
- if pd.notna(gross_profit) and gross_profit > 0:
457
- st.metric("Gross Profit", f"${gross_profit/1e9:.2f}B" if gross_profit > 1e9 else f"${gross_profit/1e6:.2f}M")
458
- else:
459
- st.metric("Gross Profit", "N/A")
460
-
461
- with fin_col4:
462
- operating_income = latest_income.get('operating_income', 0)
463
- if pd.notna(operating_income) and operating_income > 0:
464
- st.metric("Operating Income", f"${operating_income/1e9:.2f}B" if operating_income > 1e9 else f"${operating_income/1e6:.2f}M")
465
- else:
466
- st.metric("Operating Income", "N/A")
467
-
468
- # Additional metrics
469
- fin_col5, fin_col6, fin_col7, fin_col8 = st.columns(4)
470
-
471
- with fin_col5:
472
- eps = latest_income.get('diluted_earnings_per_share', 0)
473
- if pd.notna(eps):
474
- st.metric("EPS (Diluted)", f"${eps:.2f}")
475
- else:
476
- st.metric("EPS (Diluted)", "N/A")
477
-
478
- with fin_col6:
479
- ebitda = latest_income.get('ebitda', 0)
480
- if pd.notna(ebitda) and ebitda > 0:
481
- st.metric("EBITDA", f"${ebitda/1e9:.2f}B" if ebitda > 1e9 else f"${ebitda/1e6:.2f}M")
482
- else:
483
- st.metric("EBITDA", "N/A")
484
-
485
- with fin_col7:
486
- cogs = latest_income.get('cost_of_revenue', 0)
487
- if pd.notna(cogs) and cogs > 0:
488
- st.metric("Cost of Revenue", f"${cogs/1e9:.2f}B" if cogs > 1e9 else f"${cogs/1e6:.2f}M")
489
- else:
490
- st.metric("Cost of Revenue", "N/A")
491
-
492
- with fin_col8:
493
- rd_expense = latest_income.get('research_and_development_expense', 0)
494
- if pd.notna(rd_expense) and rd_expense > 0:
495
- st.metric("R&D Expense", f"${rd_expense/1e9:.2f}B" if rd_expense > 1e9 else f"${rd_expense/1e6:.2f}M")
496
- else:
497
- st.metric("R&D Expense", "N/A")
498
-
499
- # Financial history chart
500
- st.markdown('<div class="section-title">๐Ÿ“Š Revenue & Net Income Trend</div>', unsafe_allow_html=True)
501
 
502
- # Prepare data for chart
503
- if len(income_stmt) > 0:
504
  income_chart_data = income_stmt[['period_ending', 'total_revenue', 'net_income']].dropna()
505
 
506
  if len(income_chart_data) > 0:
507
- fig_financial = go.Figure()
508
-
509
- fig_financial.add_trace(go.Bar(
510
- x=income_chart_data['period_ending'],
511
- y=income_chart_data['total_revenue'],
512
- name="Total Revenue",
513
- marker=dict(color='#0066ff', opacity=0.9),
514
- yaxis='y1'
515
- ))
516
-
517
- fig_financial.add_trace(go.Bar(
518
- x=income_chart_data['period_ending'],
519
- y=income_chart_data['net_income'],
520
- name="Net Income",
521
- marker=dict(color='#00d084', opacity=0.9),
522
- yaxis='y1'
523
- ))
524
-
525
- fig_financial.update_layout(
526
- title="Revenue & Net Income (Annual)",
527
- xaxis_title="Period",
528
- yaxis_title="Amount ($)",
529
- hovermode="x unified",
530
- template="plotly_dark",
531
- height=400,
532
- barmode='group',
533
- margin=dict(l=0, r=0, t=40, b=0),
534
- plot_bgcolor="#0d1117",
535
- paper_bgcolor="#0e1117",
536
- font=dict(color="#e6edf3", size=12, family="Arial, sans-serif"),
537
- xaxis=dict(
538
- gridcolor="#30363d",
539
- showgrid=True,
540
- zeroline=False,
541
- color="#8b949e"
542
- ),
543
- yaxis=dict(
544
- gridcolor="#30363d",
545
- showgrid=True,
546
- zeroline=False,
547
- color="#8b949e"
548
- ),
549
- legend=dict(
550
- bgcolor="rgba(13, 17, 23, 0.8)",
551
- bordercolor="#30363d",
552
- borderwidth=1,
553
- font=dict(color="#e6edf3")
554
- ),
555
- hoverlabel=dict(
556
- bgcolor="#0d1117",
557
- bordercolor="#30363d",
558
- font=dict(color="#e6edf3")
559
- )
560
- )
561
-
562
  st.plotly_chart(fig_financial, use_container_width=True)
563
 
564
- # ---- Tabs ----
565
- tab1, tab2, tab3, tab4 = st.tabs(["๐Ÿ“ˆ Price & Moving Averages", "๐Ÿ“Š RSI Indicator", "๐Ÿ“‰ TradingView", "๐Ÿ“‹ Financials"])
566
-
567
- # ---- Tab 1: Price + SMA/EMA ----
568
- with tab1:
569
- fig_price = go.Figure()
570
-
571
- fig_price.add_trace(go.Scatter(
572
- x=df.index, y=df["close"],
573
- name="Close Price",
574
- line=dict(color="#0066ff", width=2.5)
575
- ))
576
- fig_price.add_trace(go.Scatter(
577
- x=df.index, y=df["SMA"],
578
- name=f"SMA {period}",
579
- line=dict(color="#00d084", width=2, dash="dash")
580
- ))
581
- fig_price.add_trace(go.Scatter(
582
- x=df.index, y=df["EMA"],
583
- name=f"EMA {period}",
584
- line=dict(color="#ffa500", width=2, dash="dot")
585
- ))
586
-
587
- fig_price.update_layout(
588
- title=f"{symbol} - Price with Moving Averages",
589
- xaxis_title="Date",
590
- yaxis_title="Price ($)",
591
- hovermode="x unified",
592
- template="plotly_dark",
593
- height=500,
594
- margin=dict(l=0, r=0, t=40, b=0),
595
- plot_bgcolor="#0d1117",
596
- paper_bgcolor="#0e1117",
597
- font=dict(color="#e6edf3", size=12, family="Arial, sans-serif"),
598
- xaxis=dict(
599
- gridcolor="#30363d",
600
- showgrid=True,
601
- zeroline=False,
602
- color="#8b949e"
603
- ),
604
- yaxis=dict(
605
- gridcolor="#30363d",
606
- showgrid=True,
607
- zeroline=False,
608
- color="#8b949e"
609
- ),
610
- legend=dict(
611
- bgcolor="rgba(13, 17, 23, 0.8)",
612
- bordercolor="#30363d",
613
- borderwidth=1,
614
- font=dict(color="#e6edf3")
615
- ),
616
- hoverlabel=dict(
617
- bgcolor="#0d1117",
618
- bordercolor="#30363d",
619
- font=dict(color="#e6edf3")
620
- )
621
- )
622
-
623
- st.plotly_chart(fig_price, use_container_width=True)
624
-
625
- # ---- Tab 2: RSI ----
626
- with tab2:
627
- fig_rsi = go.Figure()
628
-
629
- fig_rsi.add_trace(go.Scatter(
630
- x=df.index, y=df["RSI"],
631
- name="RSI",
632
- line=dict(color="#ff3838", width=2.5),
633
- fill="tozeroy",
634
- fillcolor="rgba(255, 56, 56, 0.15)"
635
- ))
636
-
637
- # Add overbought/oversold lines
638
- fig_rsi.add_hline(y=70, line_dash="dash", line_color="rgba(255, 165, 0, 0.6)", annotation_text="Overbought (70)")
639
- fig_rsi.add_hline(y=30, line_dash="dash", line_color="rgba(0, 208, 132, 0.6)", annotation_text="Oversold (30)")
640
- fig_rsi.add_hline(y=50, line_dash="dot", line_color="rgba(139, 148, 158, 0.3)")
641
-
642
- fig_rsi.update_layout(
643
- title=f"{symbol} - Relative Strength Index (RSI)",
644
- xaxis_title="Date",
645
- yaxis_title="RSI",
646
- hovermode="x unified",
647
- template="plotly_dark",
648
- height=500,
649
- margin=dict(l=0, r=0, t=40, b=0),
650
- plot_bgcolor="#0d1117",
651
- paper_bgcolor="#0e1117",
652
- font=dict(color="#e6edf3", size=12, family="Arial, sans-serif"),
653
- xaxis=dict(
654
- gridcolor="#30363d",
655
- showgrid=True,
656
- zeroline=False,
657
- color="#8b949e"
658
- ),
659
- yaxis=dict(
660
- range=[0, 100],
661
- gridcolor="#30363d",
662
- showgrid=True,
663
- zeroline=False,
664
- color="#8b949e"
665
- ),
666
- legend=dict(
667
- bgcolor="rgba(13, 17, 23, 0.8)",
668
- bordercolor="#30363d",
669
- borderwidth=1,
670
- font=dict(color="#e6edf3")
671
- ),
672
- hoverlabel=dict(
673
- bgcolor="#0d1117",
674
- bordercolor="#30363d",
675
- font=dict(color="#e6edf3")
676
- )
677
- )
678
-
679
- st.plotly_chart(fig_rsi, use_container_width=True)
680
-
681
- # ---- Tab 3: TradingView ----
682
- with tab3:
683
- tradingview_html = f"""
684
- <div class="tradingview-widget-container">
685
- <div id="tradingview_{symbol}"></div>
686
- <script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
687
- <script type="text/javascript">
688
- new TradingView.widget({{
689
- "width": "100%",
690
- "height": 600,
691
- "symbol": "{symbol}",
692
- "interval": "D",
693
- "timezone": "Etc/UTC",
694
- "theme": "dark",
695
- "style": "1",
696
- "locale": "en",
697
- "enable_publishing": false,
698
- "allow_symbol_change": true,
699
- "container_id": "tradingview_{symbol}"
700
- }});
701
- </script>
702
- </div>
703
- """
704
- st.components.v1.html(tradingview_html, height=650)
705
-
706
- # ---- Tab 4: Detailed Financials ----
707
- with tab4:
708
- st.markdown("### Income Statement")
709
-
710
- if not income_stmt.empty:
711
- # Select key columns to display
712
- display_columns = [
713
- 'period_ending',
714
- 'total_revenue',
715
- 'cost_of_revenue',
716
- 'gross_profit',
717
- 'operating_income',
718
- 'net_income',
719
- 'diluted_earnings_per_share',
720
- 'ebitda'
721
- ]
722
-
723
- # Filter to available columns
724
- available_cols = [col for col in display_columns if col in income_stmt.columns]
725
- financial_display = income_stmt[available_cols].copy()
726
-
727
- # Format numeric columns
728
- for col in financial_display.columns:
729
- if col != 'period_ending':
730
- financial_display[col] = financial_display[col].apply(
731
- lambda x: f"${x/1e9:.2f}B" if pd.notna(x) and abs(x) >= 1e9 else (
732
- f"${x/1e6:.2f}M" if pd.notna(x) and abs(x) >= 1e6 else (
733
- f"${x:.2f}" if pd.notna(x) else "N/A"
734
- )
735
- )
736
- )
737
-
738
- st.dataframe(financial_display, use_container_width=True, hide_index=True)
739
-
740
- # Profitability metrics
741
- st.markdown("### Profitability Metrics")
742
-
743
- prof_col1, prof_col2 = st.columns(2)
744
-
745
- with prof_col1:
746
- # Calculate profit margins
747
- latest_data = income_stmt.iloc[0]
748
- total_rev = latest_data.get('total_revenue', 0)
749
- gross_prof = latest_data.get('gross_profit', 0)
750
- net_inc = latest_data.get('net_income', 0)
751
-
752
- if total_rev and total_rev > 0:
753
- gross_margin = (gross_prof / total_rev) * 100 if pd.notna(gross_prof) else 0
754
- net_margin = (net_inc / total_rev) * 100 if pd.notna(net_inc) else 0
755
-
756
- st.metric("Gross Margin", f"{gross_margin:.2f}%")
757
- st.metric("Net Profit Margin", f"{net_margin:.2f}%")
758
-
759
- with prof_col2:
760
- operating_inc = latest_data.get('operating_income', 0)
761
- if total_rev and total_rev > 0 and operating_inc:
762
- operating_margin = (operating_inc / total_rev) * 100
763
- st.metric("Operating Margin", f"{operating_margin:.2f}%")
764
-
765
- # Growth comparison
766
- if len(income_stmt) > 1:
767
- prev_revenue = income_stmt.iloc[1].get('total_revenue', 0)
768
- if prev_revenue and prev_revenue > 0:
769
- revenue_growth = ((total_rev - prev_revenue) / prev_revenue) * 100
770
- st.metric("Revenue Growth (YoY)", f"{revenue_growth:+.2f}%")
771
-
772
- except Exception as e:
773
- st.error(f"Error loading data for {symbol}: {str(e)}")
774
- st.info("Please check the ticker symbol and try again.")
 
1
+ """Financial Analysis Dashboard - Main Application."""
2
+
3
  import streamlit as st
 
 
 
4
  from dotenv import load_dotenv
5
  import os
6
 
7
+ from styles import DARK_THEME_CSS
8
+ from data import (
9
+ load_stock_data,
10
+ load_company_profile,
11
+ load_income_statement,
12
+ calculate_technical_indicators,
13
+ get_price_metrics,
14
+ )
15
+ from charts import (
16
+ create_price_chart,
17
+ create_rsi_chart,
18
+ create_financial_chart,
19
+ )
20
+ from ui import (
21
+ display_price_metrics,
22
+ display_company_info,
23
+ display_financial_metrics,
24
+ display_income_statement,
25
+ display_profitability_metrics,
26
+ )
27
+
28
 
29
+ # ---- Configuration ----
30
  load_dotenv()
31
  token = os.getenv("TOKEN")
32
 
 
33
  st.set_page_config(
34
  page_title="Financial Dashboard",
35
  page_icon="๐Ÿ“ˆ",
 
40
  }
41
  )
42
 
43
+ # ---- Apply Dark Theme ----
44
+ st.markdown(DARK_THEME_CSS, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  # ---- Header ----
47
  st.markdown("# ๐Ÿ“ˆ Financial Analysis Dashboard")
 
57
  st.markdown("### About")
58
  st.info("This dashboard provides real-time technical analysis with comprehensive financial metrics.")
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ def main():
62
+ """Main application logic."""
63
+ if st.button("๏ฟฝ๏ฟฝ Load Dashboard", key="load_btn", use_container_width=True):
64
+ try:
65
+ # Load data
66
+ with st.spinner("Loading data..."):
67
+ df = load_stock_data(symbol)
68
+ profile_info = load_company_profile(symbol)
69
+ income_stmt = load_income_statement(symbol)
70
 
71
+ # Calculate technical indicators
72
+ df = calculate_technical_indicators(df, period)
73
 
74
+ # Display price metrics
75
+ metrics = get_price_metrics(df)
76
+ display_price_metrics(metrics)
77
 
78
+ # Display company information
79
+ display_company_info(profile_info)
 
 
 
 
80
 
81
+ # Display financial metrics
82
+ if not income_stmt.empty:
83
+ display_financial_metrics(income_stmt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ # Financial history chart
86
+ st.markdown('<div class="section-title">๐Ÿ“Š Revenue & Net Income Trend</div>', unsafe_allow_html=True)
87
  income_chart_data = income_stmt[['period_ending', 'total_revenue', 'net_income']].dropna()
88
 
89
  if len(income_chart_data) > 0:
90
+ fig_financial = create_financial_chart(income_chart_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  st.plotly_chart(fig_financial, use_container_width=True)
92
 
93
+ # ---- Tabs ----
94
+ tab1, tab2, tab3, tab4 = st.tabs([
95
+ "๐Ÿ“ˆ Price & Moving Averages",
96
+ "๐Ÿ“Š RSI Indicator",
97
+ "๐Ÿ“‰ TradingView",
98
+ "๐Ÿ“‹ Financials"
99
+ ])
100
+
101
+ # Tab 1: Price & Moving Averages
102
+ with tab1:
103
+ fig_price = create_price_chart(df, symbol, period)
104
+ st.plotly_chart(fig_price, use_container_width=True)
105
+
106
+ # Tab 2: RSI Indicator
107
+ with tab2:
108
+ fig_rsi = create_rsi_chart(df, symbol)
109
+ st.plotly_chart(fig_rsi, use_container_width=True)
110
+
111
+ # Tab 3: TradingView
112
+ with tab3:
113
+ tradingview_html = f"""
114
+ <div class="tradingview-widget-container">
115
+ <div id="tradingview_{symbol}"></div>
116
+ <script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
117
+ <script type="text/javascript">
118
+ new TradingView.widget({{
119
+ "width": "100%",
120
+ "height": 600,
121
+ "symbol": "{symbol}",
122
+ "interval": "D",
123
+ "timezone": "Etc/UTC",
124
+ "theme": "dark",
125
+ "style": "1",
126
+ "locale": "en",
127
+ "enable_publishing": false,
128
+ "allow_symbol_change": true,
129
+ "container_id": "tradingview_{symbol}"
130
+ }});
131
+ </script>
132
+ </div>
133
+ """
134
+ st.components.v1.html(tradingview_html, height=650)
135
+
136
+ # Tab 4: Detailed Financials
137
+ with tab4:
138
+ if not income_stmt.empty:
139
+ display_income_statement(income_stmt)
140
+ display_profitability_metrics(income_stmt)
141
+
142
+ except Exception as e:
143
+ st.error(f"Error loading data for {symbol}: {str(e)}")
144
+ st.info("Please check the ticker symbol and try again.")
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/styles.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dark theme CSS styles for the financial dashboard."""
2
+
3
+ DARK_THEME_CSS = """
4
+ <style>
5
+ :root {
6
+ --primary-color: #0066ff;
7
+ --secondary-color: #1f77e2;
8
+ --success-color: #00d084;
9
+ --danger-color: #ff3838;
10
+ --warning-color: #ffa500;
11
+ --bg-dark: #0e1117;
12
+ --bg-darker: #010409;
13
+ --text-primary: #e6edf3;
14
+ --text-secondary: #8b949e;
15
+ --border-color: #30363d;
16
+ }
17
+
18
+ /* Main background */
19
+ html, body {
20
+ background-color: var(--bg-darker) !important;
21
+ color: var(--text-primary) !important;
22
+ margin: 0 !important;
23
+ padding: 0 !important;
24
+ }
25
+
26
+ /* Streamlit containers */
27
+ .main, [data-testid="stAppViewContainer"] {
28
+ background-color: var(--bg-dark) !important;
29
+ }
30
+
31
+ /* Hide header and footer */
32
+ [data-testid="stHeader"] {
33
+ background-color: var(--bg-dark) !important;
34
+ }
35
+
36
+ [data-testid="stToolbar"] {
37
+ background-color: var(--bg-dark) !important;
38
+ }
39
+
40
+ .stApp {
41
+ background-color: var(--bg-dark) !important;
42
+ }
43
+
44
+ [data-testid="stDecoration"] {
45
+ background-color: var(--bg-dark) !important;
46
+ }
47
+
48
+ [data-testid="stSidebar"] {
49
+ background-color: #0d1117 !important;
50
+ border-right: 1px solid var(--border-color);
51
+ }
52
+
53
+ /* Text colors */
54
+ p, span, div, h1, h2, h3, h4, h5, h6, label, li, a {
55
+ color: var(--text-primary) !important;
56
+ }
57
+
58
+ /* Headings */
59
+ h1, h2, h3 {
60
+ color: var(--text-primary) !important;
61
+ font-weight: 700 !important;
62
+ }
63
+
64
+ /* Links */
65
+ a {
66
+ color: var(--primary-color) !important;
67
+ text-decoration: none !important;
68
+ }
69
+
70
+ a:hover {
71
+ color: var(--secondary-color) !important;
72
+ text-decoration: underline !important;
73
+ }
74
+
75
+ /* Labels and text inputs */
76
+ label {
77
+ color: var(--text-primary) !important;
78
+ font-weight: 500 !important;
79
+ }
80
+
81
+ /* Paragraph text */
82
+ p {
83
+ color: var(--text-primary) !important;
84
+ line-height: 1.6 !important;
85
+ }
86
+
87
+ /* Metric card styling */
88
+ [data-testid="metric-container"] {
89
+ background: linear-gradient(135deg, #1f2937 0%, #111827 100%) !important;
90
+ border: 1px solid var(--border-color) !important;
91
+ border-radius: 10px !important;
92
+ padding: 1.5rem !important;
93
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3) !important;
94
+ }
95
+
96
+ .metric-card {
97
+ background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
98
+ padding: 1.5rem;
99
+ border-radius: 10px;
100
+ border: 1px solid var(--border-color);
101
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
102
+ }
103
+
104
+ .metric-value {
105
+ font-size: 2.5rem;
106
+ font-weight: 700;
107
+ color: var(--primary-color);
108
+ margin: 0.5rem 0;
109
+ }
110
+
111
+ .metric-label {
112
+ font-size: 0.875rem;
113
+ color: var(--text-secondary);
114
+ text-transform: uppercase;
115
+ letter-spacing: 0.05em;
116
+ }
117
+
118
+ .section-title {
119
+ color: var(--text-primary);
120
+ border-bottom: 2px solid var(--primary-color);
121
+ padding-bottom: 1rem;
122
+ margin-top: 2rem;
123
+ margin-bottom: 1.5rem;
124
+ }
125
+
126
+ /* Button styling */
127
+ .stButton > button {
128
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important;
129
+ color: #ffffff !important;
130
+ border: none !important;
131
+ border-radius: 8px !important;
132
+ padding: 0.75rem 2rem !important;
133
+ font-weight: 700 !important;
134
+ transition: all 0.3s ease !important;
135
+ box-shadow: 0 4px 6px rgba(0, 102, 255, 0.2) !important;
136
+ }
137
+
138
+ .stButton > button:hover {
139
+ box-shadow: 0 8px 16px rgba(0, 102, 255, 0.4) !important;
140
+ transform: translateY(-2px) !important;
141
+ }
142
+
143
+ .stButton > button:active {
144
+ transform: translateY(0) !important;
145
+ }
146
+
147
+ /* Input fields */
148
+ [data-testid="stTextInput"] input,
149
+ [data-testid="stSlider"] input {
150
+ background-color: #161b22 !important;
151
+ border: 1px solid var(--border-color) !important;
152
+ color: var(--text-primary) !important;
153
+ border-radius: 6px !important;
154
+ }
155
+
156
+ [data-testid="stTextInput"] input::placeholder {
157
+ color: var(--text-secondary) !important;
158
+ }
159
+
160
+ /* Slider */
161
+ [data-testid="stSlider"] {
162
+ color: var(--primary-color) !important;
163
+ }
164
+
165
+ /* Tabs */
166
+ [data-testid="stTabs"] [role="tablist"] {
167
+ background-color: transparent !important;
168
+ border-bottom: 2px solid var(--border-color) !important;
169
+ }
170
+
171
+ [data-testid="stTabs"] [role="tab"] {
172
+ color: var(--text-secondary) !important;
173
+ background-color: transparent !important;
174
+ border: none !important;
175
+ padding: 1rem 1.5rem !important;
176
+ }
177
+
178
+ [data-testid="stTabs"] [role="tab"][aria-selected="true"] {
179
+ color: var(--primary-color) !important;
180
+ border-bottom: 3px solid var(--primary-color) !important;
181
+ }
182
+
183
+ /* Dataframe */
184
+ [data-testid="dataframe"] {
185
+ background-color: #0d1117 !important;
186
+ }
187
+
188
+ .dataframe {
189
+ background-color: #0d1117 !important;
190
+ color: var(--text-primary) !important;
191
+ }
192
+
193
+ /* Info/Error boxes */
194
+ [data-testid="stInfo"],
195
+ [data-testid="stSuccess"],
196
+ [data-testid="stWarning"],
197
+ [data-testid="stError"] {
198
+ background-color: rgba(0, 102, 255, 0.1) !important;
199
+ border-left: 4px solid var(--primary-color) !important;
200
+ border-radius: 6px !important;
201
+ }
202
+
203
+ [data-testid="stError"] {
204
+ background-color: rgba(255, 56, 56, 0.1) !important;
205
+ border-left-color: var(--danger-color) !important;
206
+ }
207
+
208
+ /* Markdown */
209
+ [data-testid="stMarkdown"] {
210
+ color: var(--text-primary) !important;
211
+ }
212
+
213
+ /* Expander */
214
+ [data-testid="stExpander"] {
215
+ background-color: #161b22 !important;
216
+ border: 1px solid var(--border-color) !important;
217
+ border-radius: 6px !important;
218
+ }
219
+
220
+ /* Metric text styling */
221
+ [data-testid="metric-container"] p {
222
+ color: var(--text-primary) !important;
223
+ }
224
+
225
+ [data-testid="metric-container"] [data-testid="stMetricValue"] {
226
+ color: var(--primary-color) !important;
227
+ font-weight: 700 !important;
228
+ }
229
+
230
+ /* Slider label color */
231
+ [data-testid="stSlider"] label {
232
+ color: var(--text-primary) !important;
233
+ }
234
+
235
+ /* Text input label */
236
+ [data-testid="stTextInput"] label {
237
+ color: var(--text-primary) !important;
238
+ }
239
+
240
+ /* Write and markdown text */
241
+ [data-testid="stMarkdownContainer"] p {
242
+ color: var(--text-primary) !important;
243
+ }
244
+
245
+ [data-testid="stMarkdownContainer"] strong {
246
+ color: var(--primary-color) !important;
247
+ font-weight: 600 !important;
248
+ }
249
+
250
+ /* Spinner text */
251
+ [data-testid="stSpinner"] {
252
+ color: var(--primary-color) !important;
253
+ }
254
+
255
+ /* Column separators */
256
+ hr {
257
+ border-color: var(--border-color) !important;
258
+ }
259
+
260
+ /* Scrollbar */
261
+ ::-webkit-scrollbar {
262
+ width: 8px;
263
+ height: 8px;
264
+ }
265
+
266
+ ::-webkit-scrollbar-track {
267
+ background: #0d1117;
268
+ }
269
+
270
+ ::-webkit-scrollbar-thumb {
271
+ background: var(--border-color);
272
+ border-radius: 4px;
273
+ }
274
+
275
+ ::-webkit-scrollbar-thumb:hover {
276
+ background: var(--primary-color);
277
+ }
278
+
279
+ /* Selection highlighting */
280
+ ::selection {
281
+ background-color: var(--primary-color);
282
+ color: #fff;
283
+ }
284
+
285
+ /* Fix all white backgrounds */
286
+ .stApp > header {
287
+ background-color: var(--bg-dark) !important;
288
+ }
289
+
290
+ .stApp > header::before {
291
+ background: none !important;
292
+ }
293
+
294
+ .stApp > header::after {
295
+ background: none !important;
296
+ }
297
+
298
+ /* Streamlit elements background */
299
+ [data-testid="stVerticalBlock"] {
300
+ background-color: transparent !important;
301
+ }
302
+
303
+ [data-testid="stVerticalBlockBorderWrapper"] {
304
+ background-color: transparent !important;
305
+ }
306
+
307
+ /* Remove white decorative elements */
308
+ .st-emotion-cache-1gvbgyg {
309
+ background-color: var(--bg-dark) !important;
310
+ }
311
+
312
+ .st-emotion-cache-1jicfl2 {
313
+ background-color: var(--bg-dark) !important;
314
+ }
315
+
316
+ /* Ensure all root divs are dark */
317
+ div[class*="st-"] {
318
+ background-color: transparent !important;
319
+ }
320
+
321
+ /* Modal and overlay backgrounds */
322
+ .stModal {
323
+ background-color: var(--bg-dark) !important;
324
+ }
325
+
326
+ /* Alert boxes background */
327
+ .stAlert {
328
+ background-color: rgba(0, 102, 255, 0.1) !important;
329
+ }
330
+ </style>
331
+ """
app/ui.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """UI component functions for the financial dashboard."""
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ from data import format_financial_value, get_profitability_metrics
6
+
7
+
8
+ def display_price_metrics(metrics: dict):
9
+ """Display key price metrics in columns."""
10
+ st.markdown('<div class="section-title">๐Ÿ“Š Price Metrics</div>', unsafe_allow_html=True)
11
+
12
+ col1, col2, col3, col4 = st.columns(4)
13
+
14
+ with col1:
15
+ st.metric("Current Price", f"${metrics['current_price']:.2f}",
16
+ f"{metrics['price_change']:+.2f}", delta_color="normal")
17
+
18
+ with col2:
19
+ st.metric("Day Change %", f"{metrics['price_change_pct']:+.2f}%",
20
+ None, delta_color="normal")
21
+
22
+ with col3:
23
+ st.metric("52W High", f"${metrics['high_52w']:.2f}")
24
+
25
+ with col4:
26
+ st.metric("52W Low", f"${metrics['low_52w']:.2f}")
27
+
28
+
29
+ def display_company_info(profile_info):
30
+ """Display company information."""
31
+ st.markdown('<div class="section-title">๐Ÿ“‹ Company Information</div>', unsafe_allow_html=True)
32
+
33
+ if profile_info:
34
+ info_col1, info_col2 = st.columns(2)
35
+ with info_col1:
36
+ st.write(f"**Company Name:** {getattr(profile_info, 'name', 'N/A')}")
37
+ st.write(f"**Sector:** {getattr(profile_info, 'sector', 'N/A')}")
38
+ st.write(f"**Industry:** {getattr(profile_info, 'industry', 'N/A')}")
39
+
40
+ with info_col2:
41
+ st.write(f"**Country:** {getattr(profile_info, 'country', 'N/A')}")
42
+ st.write(f"**Exchange:** {getattr(profile_info, 'exchange', 'N/A')}")
43
+ st.write(f"**Website:** {getattr(profile_info, 'website', 'N/A')}")
44
+
45
+
46
+ def display_financial_metrics(income_stmt: pd.DataFrame):
47
+ """Display financial metrics from income statement."""
48
+ st.markdown('<div class="section-title">๐Ÿ’ฐ Financial Metrics</div>', unsafe_allow_html=True)
49
+
50
+ latest_income = income_stmt.iloc[0] if len(income_stmt) > 0 else None
51
+
52
+ if latest_income is not None:
53
+ # First row of metrics
54
+ fin_col1, fin_col2, fin_col3, fin_col4 = st.columns(4)
55
+
56
+ with fin_col1:
57
+ revenue = latest_income.get('total_revenue', 0)
58
+ if pd.notna(revenue) and revenue > 0:
59
+ st.metric("Total Revenue", format_financial_value(revenue))
60
+ else:
61
+ st.metric("Total Revenue", "N/A")
62
+
63
+ with fin_col2:
64
+ net_income = latest_income.get('net_income', 0)
65
+ if pd.notna(net_income) and net_income > 0:
66
+ st.metric("Net Income", format_financial_value(net_income))
67
+ else:
68
+ st.metric("Net Income", "N/A")
69
+
70
+ with fin_col3:
71
+ gross_profit = latest_income.get('gross_profit', 0)
72
+ if pd.notna(gross_profit) and gross_profit > 0:
73
+ st.metric("Gross Profit", format_financial_value(gross_profit))
74
+ else:
75
+ st.metric("Gross Profit", "N/A")
76
+
77
+ with fin_col4:
78
+ operating_income = latest_income.get('operating_income', 0)
79
+ if pd.notna(operating_income) and operating_income > 0:
80
+ st.metric("Operating Income", format_financial_value(operating_income))
81
+ else:
82
+ st.metric("Operating Income", "N/A")
83
+
84
+ # Second row of metrics
85
+ fin_col5, fin_col6, fin_col7, fin_col8 = st.columns(4)
86
+
87
+ with fin_col5:
88
+ eps = latest_income.get('diluted_earnings_per_share', 0)
89
+ if pd.notna(eps):
90
+ st.metric("EPS (Diluted)", f"${eps:.2f}")
91
+ else:
92
+ st.metric("EPS (Diluted)", "N/A")
93
+
94
+ with fin_col6:
95
+ ebitda = latest_income.get('ebitda', 0)
96
+ if pd.notna(ebitda) and ebitda > 0:
97
+ st.metric("EBITDA", format_financial_value(ebitda))
98
+ else:
99
+ st.metric("EBITDA", "N/A")
100
+
101
+ with fin_col7:
102
+ cogs = latest_income.get('cost_of_revenue', 0)
103
+ if pd.notna(cogs) and cogs > 0:
104
+ st.metric("Cost of Revenue", format_financial_value(cogs))
105
+ else:
106
+ st.metric("Cost of Revenue", "N/A")
107
+
108
+ with fin_col8:
109
+ rd_expense = latest_income.get('research_and_development_expense', 0)
110
+ if pd.notna(rd_expense) and rd_expense > 0:
111
+ st.metric("R&D Expense", format_financial_value(rd_expense))
112
+ else:
113
+ st.metric("R&D Expense", "N/A")
114
+
115
+
116
+ def display_income_statement(income_stmt: pd.DataFrame):
117
+ """Display formatted income statement table."""
118
+ st.markdown("### Income Statement")
119
+
120
+ if not income_stmt.empty:
121
+ display_columns = [
122
+ 'period_ending',
123
+ 'total_revenue',
124
+ 'cost_of_revenue',
125
+ 'gross_profit',
126
+ 'operating_income',
127
+ 'net_income',
128
+ 'diluted_earnings_per_share',
129
+ 'ebitda'
130
+ ]
131
+
132
+ available_cols = [col for col in display_columns if col in income_stmt.columns]
133
+ financial_display = income_stmt[available_cols].copy()
134
+
135
+ for col in financial_display.columns:
136
+ if col != 'period_ending':
137
+ financial_display[col] = financial_display[col].apply(
138
+ lambda x: format_financial_value(x)
139
+ )
140
+
141
+ st.dataframe(financial_display, use_container_width=True, hide_index=True)
142
+
143
+
144
+ def display_profitability_metrics(income_stmt: pd.DataFrame):
145
+ """Display profitability metrics."""
146
+ st.markdown("### Profitability Metrics")
147
+
148
+ prof_col1, prof_col2 = st.columns(2)
149
+ latest_data = income_stmt.iloc[0]
150
+ metrics = get_profitability_metrics(latest_data)
151
+
152
+ with prof_col1:
153
+ if "gross_margin" in metrics:
154
+ st.metric("Gross Margin", f"{metrics['gross_margin']:.2f}%")
155
+ if "net_margin" in metrics:
156
+ st.metric("Net Profit Margin", f"{metrics['net_margin']:.2f}%")
157
+
158
+ with prof_col2:
159
+ if "operating_margin" in metrics:
160
+ st.metric("Operating Margin", f"{metrics['operating_margin']:.2f}%")
161
+
162
+ if len(income_stmt) > 1:
163
+ prev_revenue = income_stmt.iloc[1].get('total_revenue', 0)
164
+ total_rev = latest_data.get('total_revenue', 0)
165
+ if prev_revenue and prev_revenue > 0:
166
+ revenue_growth = ((total_rev - prev_revenue) / prev_revenue) * 100
167
+ st.metric("Revenue Growth (YoY)", f"{revenue_growth:+.2f}%")