tosanoob commited on
Commit
57d91e6
·
1 Parent(s): dec9e8e

feat: change UI appearance

Browse files
Files changed (7) hide show
  1. .gitignore +1 -2
  2. .streamlit/config.toml +26 -0
  3. .streamlit/custom.py +130 -0
  4. Home.py +48 -27
  5. app.py +24 -0
  6. pages/chat_app.py +63 -47
  7. pages/stock_report.py +334 -320
.gitignore CHANGED
@@ -1,4 +1,3 @@
1
  .env
2
  PLAN.md
3
- __pycache__/
4
- .streamlit/
 
1
  .env
2
  PLAN.md
3
+ __pycache__/
 
.streamlit/config.toml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [theme]
2
+ primaryColor = "#3498db"
3
+ backgroundColor = "#f9f9f9"
4
+ secondaryBackgroundColor = "#ffffff"
5
+ textColor = "#333333"
6
+ font = "sans serif"
7
+
8
+ [server]
9
+ enableCORS = false
10
+ enableXsrfProtection = true
11
+ runOnSave = true
12
+ maxUploadSize = 200
13
+ headless = true
14
+
15
+ [browser]
16
+ gatherUsageStats = false
17
+ serverAddress = "0.0.0.0"
18
+ serverPort = 8501
19
+
20
+ [runner]
21
+ fastReruns = true
22
+
23
+ [client]
24
+ showErrorDetails = false
25
+ caching = true
26
+ displayEnabled = true
.streamlit/custom.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ def apply_custom_css():
4
+ """Apply custom CSS to fix UI issues on Huggingface Spaces"""
5
+ st.markdown("""
6
+ <style>
7
+ /* Fix for stacking UI issues */
8
+ .element-container {
9
+ overflow-anchor: none !important;
10
+ }
11
+
12
+ /* Fix for main content area */
13
+ .stApp {
14
+ min-height: 100vh;
15
+ overflow-y: auto;
16
+ }
17
+
18
+ /* Improve sidebar styling */
19
+ .css-1d391kg {
20
+ padding-top: 2rem;
21
+ }
22
+
23
+ /* Fix for scrolling issues */
24
+ .main .block-container {
25
+ max-width: 100%;
26
+ padding: 1rem;
27
+ }
28
+
29
+ /* Hide empty elements */
30
+ .element-container:empty {
31
+ display: none;
32
+ }
33
+
34
+ /* Custom tabs styling */
35
+ .stTabs [data-baseweb="tab-list"] {
36
+ gap: 2px;
37
+ }
38
+
39
+ .stTabs [data-baseweb="tab"] {
40
+ height: 50px;
41
+ white-space: pre-wrap;
42
+ background-color: #F0F2F6;
43
+ border-radius: 4px 4px 0 0;
44
+ gap: 1px;
45
+ padding-top: 10px;
46
+ padding-bottom: 10px;
47
+ }
48
+
49
+ .stTabs [aria-selected="true"] {
50
+ background-color: #FFFFFF !important;
51
+ border-bottom: 2px solid #4CAF50;
52
+ }
53
+
54
+ /* Chat container styling */
55
+ [data-testid="stChatMessageContent"] {
56
+ background-color: #F0F2F6;
57
+ border-radius: 10px;
58
+ padding: 8px 12px;
59
+ }
60
+
61
+ [data-testid="stChatMessage"] [data-testid="stChatMessageContent"] code {
62
+ background-color: rgba(0,0,0,0.1);
63
+ }
64
+
65
+ /* Hide duplicate header after page switches */
66
+ .duplicated-header {
67
+ display: none;
68
+ }
69
+
70
+ /* Fix for any layout issues specific to Huggingface */
71
+ @media screen and (max-width: 1200px) {
72
+ .row-widget.stButton {
73
+ width: 100%;
74
+ }
75
+ }
76
+ </style>
77
+ """, unsafe_allow_html=True)
78
+
79
+ def apply_custom_js():
80
+ """Apply custom JavaScript to fix UI issues on Huggingface Spaces"""
81
+ st.markdown("""
82
+ <script>
83
+ // Function to clean up UI by removing duplicated elements
84
+ function cleanupUI() {
85
+ // Wait for the page to fully load
86
+ setTimeout(function() {
87
+ // Find headers that might be duplicated
88
+ const headers = document.querySelectorAll('h1, h2');
89
+ const seen = new Set();
90
+
91
+ headers.forEach((header, index) => {
92
+ const text = header.innerText;
93
+ if (seen.has(text) && index > 0) {
94
+ // Mark duplicates
95
+ header.classList.add('duplicated-header');
96
+ header.style.display = 'none';
97
+ }
98
+ seen.add(text);
99
+ });
100
+
101
+ // Fix any scrolling issues
102
+ document.querySelector('.stApp').scrollTop = 0;
103
+ }, 1000);
104
+ }
105
+
106
+ // Run cleanup when DOM is ready
107
+ document.addEventListener('DOMContentLoaded', cleanupUI);
108
+
109
+ // Also run cleanup when Streamlit's internal events happen
110
+ const observer = new MutationObserver((mutations) => {
111
+ cleanupUI();
112
+ });
113
+
114
+ // Start observing changes in the main container
115
+ window.addEventListener('load', () => {
116
+ const mainContainer = document.querySelector('.main');
117
+ if (mainContainer) {
118
+ observer.observe(mainContainer, {
119
+ childList: true,
120
+ subtree: true
121
+ });
122
+ }
123
+ });
124
+ </script>
125
+ """, unsafe_allow_html=True)
126
+
127
+ def init():
128
+ """Initialize custom CSS and JS"""
129
+ apply_custom_css()
130
+ apply_custom_js()
Home.py CHANGED
@@ -3,48 +3,69 @@ import streamlit as st
3
  import os
4
  from dotenv import load_dotenv
5
  import google.generativeai as genai
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  # Load environment variables
8
  load_dotenv()
9
 
10
- # Page setup
 
 
 
 
11
  st.set_page_config(
12
  page_title="AI Financial Dashboard",
13
  page_icon="📊",
14
- layout="wide"
 
15
  )
16
 
17
- # Application title
18
- st.title("📊 AI Financial Dashboard v2.0")
19
-
20
- # Display application information
21
- st.markdown("""
22
- ## Welcome to AI Financial Dashboard
 
 
 
 
23
 
24
- This is an intelligent financial analysis application using AI to help you make smarter investment decisions.
25
 
26
- ### Main Features:
27
 
28
- 1. **💬 Chat with AI Financial Analyst**:
29
- - Search for stock information
30
- - View price charts
31
- - Convert currencies
32
- &nbsp;
33
- 2. **📄 In-depth Stock Analysis Report**:
34
- - Comprehensive analysis of a specific stock
35
- - Data collection from multiple sources
36
- - Generate in-depth reports with AI evaluation
37
- &nbsp;
38
- 3. **📰 Daily Market Summary Newsletter** (Coming soon):
39
- - Compilation of latest financial news
40
- - Categorized by topic
41
- - Daily market updates
42
 
43
- ### How to Use:
44
 
45
- Use the navigation bar on the left to switch between different features of the application.
46
 
47
- """)
48
 
49
  # Display API connection status
50
  st.sidebar.title("Connection Status")
 
3
  import os
4
  from dotenv import load_dotenv
5
  import google.generativeai as genai
6
+ import sys
7
+
8
+ # Add project root to path for imports
9
+ sys.path.append(os.path.dirname(__file__))
10
+
11
+ # Apply custom styling for Huggingface Space compatibility
12
+ try:
13
+ from .streamlit.custom import init as apply_custom_styling
14
+ apply_custom_styling()
15
+ except (ImportError, ModuleNotFoundError):
16
+ # Fallback for when running locally or if custom styling fails
17
+ pass
18
 
19
  # Load environment variables
20
  load_dotenv()
21
 
22
+ # Initialize session state if needed
23
+ if "page_viewed" not in st.session_state:
24
+ st.session_state.page_viewed = True
25
+
26
+ # Page setup - set this consistently across all pages
27
  st.set_page_config(
28
  page_title="AI Financial Dashboard",
29
  page_icon="📊",
30
+ layout="wide",
31
+ initial_sidebar_state="expanded"
32
  )
33
 
34
+ # Clear any previous runs to avoid stacking UI elements
35
+ if hasattr(st, 'empty'):
36
+ placeholder = st.empty()
37
+ with placeholder.container():
38
+ # Application title
39
+ st.title("📊 AI Financial Dashboard v2.0")
40
+
41
+ # Display application information
42
+ st.markdown("""
43
+ ## Welcome to AI Financial Dashboard
44
 
45
+ This is an intelligent financial analysis application using AI to help you make smarter investment decisions.
46
 
47
+ ### Main Features:
48
 
49
+ 1. **💬 Chat with AI Financial Analyst**:
50
+ - Search for stock information
51
+ - View price charts
52
+ - Convert currencies
53
+ &nbsp;
54
+ 2. **📄 In-depth Stock Analysis Report**:
55
+ - Comprehensive analysis of a specific stock
56
+ - Data collection from multiple sources
57
+ - Generate in-depth reports with AI evaluation
58
+ &nbsp;
59
+ 3. **📰 Daily Market Summary Newsletter** (Coming soon):
60
+ - Compilation of latest financial news
61
+ - Categorized by topic
62
+ - Daily market updates
63
 
64
+ ### How to Use:
65
 
66
+ Use the navigation bar on the left to switch between different features of the application.
67
 
68
+ """)
69
 
70
  # Display API connection status
71
  st.sidebar.title("Connection Status")
app.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Entry point for Huggingface Spaces
2
+ # This file forwards to the main Home.py file
3
+
4
+ import streamlit as st
5
+ import sys
6
+ import os
7
+
8
+ # Add the current directory to the path so that imports work correctly
9
+ sys.path.append(os.path.dirname(__file__))
10
+
11
+ # Import the Home module and run it
12
+ try:
13
+ # Apply custom styling
14
+ try:
15
+ from .streamlit.custom import init as apply_custom_styling
16
+ apply_custom_styling()
17
+ except (ImportError, ModuleNotFoundError):
18
+ pass
19
+
20
+ # Import and run the Home.py module
21
+ import Home
22
+ except Exception as e:
23
+ st.error(f"Failed to load application: {str(e)}")
24
+ st.error("Please check that the application structure is correct and Home.py exists.")
pages/chat_app.py CHANGED
@@ -13,7 +13,20 @@ from datetime import datetime
13
 
14
  # --- 1. INITIAL CONFIGURATION & STATE INITIALIZATION ---
15
  load_dotenv()
16
- st.set_page_config(layout="wide", page_title="AI Financial Dashboard")
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
18
 
19
  def initialize_state():
@@ -220,55 +233,58 @@ def render_currency_tab():
220
  else: st.error(f"Error: {res.get('error', 'Unknown')}")
221
 
222
  # --- 6. MAIN APP LAYOUT & CONTROL FLOW ---
223
- st.title("📈 AI Financial Dashboard")
 
224
 
225
- col1, col2 = st.columns([1, 1])
 
226
 
227
- with col2:
228
- right_column_container = st.container(height=600)
229
- with right_column_container:
230
- tab_names = ['Stock Watchlist', 'Time Charts', 'Currency Converter']
231
- try: default_index = tab_names.index(st.session_state.active_tab)
232
- except ValueError: default_index = 0
233
- st.session_state.active_tab = tab_names[default_index]
234
-
235
- tab1, tab2, tab3 = st.tabs(tab_names)
236
- with tab1: render_watchlist_tab()
237
- with tab2: render_timeseries_tab()
238
- with tab3: render_currency_tab()
239
 
240
- with col1:
241
- chat_container = st.container(height=600)
242
- with chat_container:
243
- for message in st.session_state.chat_history:
244
- with st.chat_message(message["role"]):
245
- st.markdown(message["parts"])
 
 
 
 
 
 
246
 
247
- user_prompt = st.chat_input("Ask AI to control the dashboard...")
248
- if user_prompt:
249
- st.session_state.chat_history.append({"role": "user", "parts": user_prompt})
250
- st.rerun()
 
 
251
 
252
- if st.session_state.chat_history and st.session_state.chat_history[-1]["role"] == "user":
253
- last_user_prompt = st.session_state.chat_history[-1]["parts"]
254
-
255
- # ***** THIS IS THE CHANGED PART *****
256
- with chat_container:
257
- with st.chat_message("model"):
258
- with st.spinner("🤖 AI executing command..."):
259
- response = st.session_state.chat_session.send_message(last_user_prompt)
260
- tool_calls = [part.function_call for part in response.candidates[0].content.parts if part.function_call]
261
- while tool_calls:
262
- tool_responses = []
263
- for call in tool_calls:
264
- func_name = call.name; func_args = {k: v for k, v in call.args.items()}
265
- if func_name in AVAILABLE_FUNCTIONS:
266
- tool_result = AVAILABLE_FUNCTIONS[func_name](**func_args)
267
- tool_responses.append(glm.Part(function_response=glm.FunctionResponse(name=func_name, response={'result': tool_result})))
268
- else:
269
- tool_responses.append(glm.Part(function_response=glm.FunctionResponse(name=func_name, response={'error': f"Function '{func_name}' not found."})))
270
- response = st.session_state.chat_session.send_message(glm.Content(parts=tool_responses))
271
  tool_calls = [part.function_call for part in response.candidates[0].content.parts if part.function_call]
272
-
273
- st.session_state.chat_history.append({"role": "model", "parts": response.text})
274
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # --- 1. INITIAL CONFIGURATION & STATE INITIALIZATION ---
15
  load_dotenv()
16
+
17
+ # Initialize session state for this page
18
+ if "chat_app_initialized" not in st.session_state:
19
+ st.session_state.chat_app_initialized = True
20
+
21
+ # Set page config consistent with other pages
22
+ st.set_page_config(
23
+ page_title="AI Financial Dashboard",
24
+ page_icon="📊",
25
+ layout="wide",
26
+ initial_sidebar_state="expanded"
27
+ )
28
+
29
+ # Configure Gemini API
30
  genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
31
 
32
  def initialize_state():
 
233
  else: st.error(f"Error: {res.get('error', 'Unknown')}")
234
 
235
  # --- 6. MAIN APP LAYOUT & CONTROL FLOW ---
236
+ # Use container to avoid UI stacking issues
237
+ main_container = st.container()
238
 
239
+ with main_container:
240
+ st.title("📈 AI Financial Dashboard")
241
 
242
+ col1, col2 = st.columns([1, 1])
 
 
 
 
 
 
 
 
 
 
 
243
 
244
+ with col2:
245
+ right_column_container = st.container(height=600)
246
+ with right_column_container:
247
+ tab_names = ['Stock Watchlist', 'Time Charts', 'Currency Converter']
248
+ try: default_index = tab_names.index(st.session_state.active_tab)
249
+ except ValueError: default_index = 0
250
+ st.session_state.active_tab = tab_names[default_index]
251
+
252
+ tab1, tab2, tab3 = st.tabs(tab_names)
253
+ with tab1: render_watchlist_tab()
254
+ with tab2: render_timeseries_tab()
255
+ with tab3: render_currency_tab()
256
 
257
+ with col1:
258
+ chat_container = st.container(height=600)
259
+ with chat_container:
260
+ for message in st.session_state.chat_history:
261
+ with st.chat_message(message["role"]):
262
+ st.markdown(message["parts"])
263
 
264
+ user_prompt = st.chat_input("Ask AI to control the dashboard...")
265
+ if user_prompt:
266
+ st.session_state.chat_history.append({"role": "user", "parts": user_prompt})
267
+ st.rerun()
268
+
269
+ if st.session_state.chat_history and st.session_state.chat_history[-1]["role"] == "user":
270
+ last_user_prompt = st.session_state.chat_history[-1]["parts"]
271
+
272
+ with chat_container:
273
+ with st.chat_message("model"):
274
+ with st.spinner("🤖 AI executing command..."):
275
+ response = st.session_state.chat_session.send_message(last_user_prompt)
 
 
 
 
 
 
 
276
  tool_calls = [part.function_call for part in response.candidates[0].content.parts if part.function_call]
277
+ while tool_calls:
278
+ tool_responses = []
279
+ for call in tool_calls:
280
+ func_name = call.name; func_args = {k: v for k, v in call.args.items()}
281
+ if func_name in AVAILABLE_FUNCTIONS:
282
+ tool_result = AVAILABLE_FUNCTIONS[func_name](**func_args)
283
+ tool_responses.append(glm.Part(function_response=glm.FunctionResponse(name=func_name, response={'result': tool_result})))
284
+ else:
285
+ tool_responses.append(glm.Part(function_response=glm.FunctionResponse(name=func_name, response={'error': f"Function '{func_name}' not found."})))
286
+ response = st.session_state.chat_session.send_message(glm.Content(parts=tool_responses))
287
+ tool_calls = [part.function_call for part in response.candidates[0].content.parts if part.function_call]
288
+
289
+ st.session_state.chat_history.append({"role": "model", "parts": response.text})
290
+ st.rerun()
pages/stock_report.py CHANGED
@@ -14,353 +14,367 @@ from datetime import datetime
14
  from modules.analysis_pipeline import run_analysis_pipeline, generate_html_report
15
  from twelvedata_api import TwelveDataAPI
16
 
17
- # Page setup
 
 
 
 
 
 
 
 
18
  st.set_page_config(
19
  page_title="Stock Analysis Report",
20
  page_icon="📊",
21
- layout="wide"
 
22
  )
23
 
24
- # Application title
25
- st.title("📄 In-depth Stock Analysis Report")
26
- st.markdown("""
27
- This application generates a comprehensive analysis report for a stock symbol, combining data from multiple sources
28
- and using AI to synthesize information, helping you make better investment decisions.
29
- """)
30
 
31
- # Function to create price chart
32
- def create_price_chart(price_data, period):
33
- """Create price chart from data"""
34
- if 'values' not in price_data:
35
- return None
36
-
37
- df = pd.DataFrame(price_data['values'])
38
- if df.empty:
39
- return None
40
-
41
- df['datetime'] = pd.to_datetime(df['datetime'])
42
- df['close'] = pd.to_numeric(df['close'])
43
-
44
- # Determine chart title based on time period
45
- title_map = {
46
- '1_month': 'Stock price over the last month',
47
- '3_months': 'Stock price over the last 3 months',
48
- '1_year': 'Stock price over the last year'
49
- }
50
-
51
- # Create chart with Altair
52
- chart = alt.Chart(df).mark_line().encode(
53
- x=alt.X('datetime:T', title='Time'),
54
- y=alt.Y('close:Q', title='Closing Price', scale=alt.Scale(zero=False)),
55
- tooltip=[
56
- alt.Tooltip('datetime:T', title='Date', format='%d/%m/%Y'),
57
- alt.Tooltip('close:Q', title='Closing Price', format=',.2f'),
58
- alt.Tooltip('volume:Q', title='Volume', format=',.0f')
59
- ]
60
- ).properties(
61
- title=title_map.get(period, f'Stock price ({period})'),
62
- height=350
63
- ).interactive()
64
-
65
- return chart
66
 
67
- # Function to convert analysis results to PDF
68
- def convert_html_to_pdf(html_content):
69
- """Convert HTML to PDF file"""
70
- with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as f:
71
- f.write(html_content.encode())
72
- temp_html = f.name
73
-
74
- pdf_bytes = weasyprint.HTML(filename=temp_html).write_pdf()
75
-
76
- # Delete temporary file after use
77
- os.unlink(temp_html)
78
-
79
- return pdf_bytes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- # Function to create PDF download link
82
- def get_download_link(pdf_bytes, filename):
83
- """Create download link for PDF file"""
84
- b64 = base64.b64encode(pdf_bytes).decode()
85
- href = f'<a href="data:application/pdf;base64,{b64}" download="{filename}">Download Report (PDF)</a>'
86
- return href
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- # List of popular stock symbols and information
89
- def load_stock_symbols():
90
- """Load stock symbols from cache or create new cache"""
91
- cache_file = "static/stock_symbols_cache.json"
92
-
93
- # Check if cache exists
94
- if os.path.exists(cache_file):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  try:
96
- with open(cache_file, 'r') as f:
97
- return json.load(f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  except Exception as e:
99
- print(f"Error loading cache: {e}")
100
-
101
- # Default list if cache doesn't exist or fails to load
102
- default_symbols = [
103
- {"symbol": "AAPL", "name": "Apple Inc."},
104
- {"symbol": "MSFT", "name": "Microsoft Corporation"},
105
- {"symbol": "GOOGL", "name": "Alphabet Inc."},
106
- {"symbol": "AMZN", "name": "Amazon.com Inc."},
107
- {"symbol": "TSLA", "name": "Tesla, Inc."},
108
- {"symbol": "META", "name": "Meta Platforms, Inc."},
109
- {"symbol": "NVDA", "name": "NVIDIA Corporation"},
110
- {"symbol": "JPM", "name": "JPMorgan Chase & Co."},
111
- {"symbol": "V", "name": "Visa Inc."},
112
- {"symbol": "JNJ", "name": "Johnson & Johnson"},
113
- {"symbol": "WMT", "name": "Walmart Inc."},
114
- {"symbol": "MA", "name": "Mastercard Incorporated"},
115
- {"symbol": "PG", "name": "Procter & Gamble Co."},
116
- {"symbol": "UNH", "name": "UnitedHealth Group Inc."},
117
- {"symbol": "HD", "name": "Home Depot Inc."},
118
- {"symbol": "BAC", "name": "Bank of America Corp."},
119
- {"symbol": "XOM", "name": "Exxon Mobil Corporation"},
120
- {"symbol": "DIS", "name": "Walt Disney Co."},
121
- {"symbol": "CSCO", "name": "Cisco Systems, Inc."},
122
- {"symbol": "VZ", "name": "Verizon Communications Inc."},
123
- {"symbol": "ADBE", "name": "Adobe Inc."},
124
- {"symbol": "NFLX", "name": "Netflix, Inc."},
125
- {"symbol": "CMCSA", "name": "Comcast Corporation"},
126
- {"symbol": "PFE", "name": "Pfizer Inc."},
127
- {"symbol": "KO", "name": "Coca-Cola Company"},
128
- {"symbol": "INTC", "name": "Intel Corporation"},
129
- {"symbol": "PYPL", "name": "PayPal Holdings, Inc."},
130
- {"symbol": "T", "name": "AT&T Inc."},
131
- {"symbol": "PEP", "name": "PepsiCo, Inc."},
132
- {"symbol": "MRK", "name": "Merck & Co., Inc."}
133
- ]
134
-
135
- # Try to fetch more comprehensive list if API key is available
136
- try:
137
- from dotenv import load_dotenv
138
- load_dotenv()
139
- api_key = os.getenv("TWELVEDATA_API_KEY")
140
- if api_key:
141
- td_api = TwelveDataAPI(api_key)
142
- stocks_data = td_api.get_all_stocks(exchange="NASDAQ")
143
- if stocks_data and 'data' in stocks_data:
144
- # Convert to format we need and take first 1000 stocks
145
- symbols = [{"symbol": stock["symbol"], "name": stock.get("name", "Unknown")}
146
- for stock in stocks_data['data']]
147
-
148
- # Save to cache
149
- os.makedirs(os.path.dirname(cache_file), exist_ok=True)
150
- with open(cache_file, 'w') as f:
151
- json.dump(symbols, f)
152
-
153
- return symbols
154
- except Exception as e:
155
- print(f"Error fetching stock symbols from API: {e}")
156
-
157
- # If everything fails, return default list
158
- return default_symbols
159
 
160
- # Load stock symbols
161
- STOCK_SYMBOLS = load_stock_symbols()
162
 
163
- # Function to format stock options for display
164
- def format_stock_option(stock):
165
- return f"{stock['symbol']} - {stock['name']}"
166
 
167
- # Create interface
168
- col1, col2 = st.columns([3, 1])
169
 
170
- # Information input section
171
- with col2:
172
- st.subheader("Enter Information")
173
-
174
- # Create a list of formatted options and a mapping back to symbols
175
- stock_options = [format_stock_option(stock) for stock in STOCK_SYMBOLS]
176
-
177
- # Use selectbox with search functionality
178
- selected_stock = st.selectbox(
179
- "Select a stock symbol",
180
- options=stock_options,
181
- index=0 if stock_options else None,
182
- placeholder="Search for a stock symbol...",
183
- )
184
-
185
- # Extract symbol from selection
186
- if selected_stock:
187
- stock_symbol = selected_stock.split(" - ")[0]
188
- else:
189
- stock_symbol = ""
190
-
191
- if st.button("Generate Report", use_container_width=True, type="primary"):
192
- if not stock_symbol:
193
- st.error("Please select a stock symbol to continue.")
194
- else:
195
- # Save stock symbol to session state to maintain between runs
196
- st.session_state.stock_symbol = stock_symbol
197
- st.session_state.analysis_requested = True
198
- st.rerun()
199
-
200
- # PDF report generation section - moved from tab1
201
- if "analysis_complete" in st.session_state and st.session_state.analysis_complete:
202
- st.divider()
203
- st.subheader("PDF Report")
204
 
205
- # Get results from session state
206
- analysis_results = st.session_state.analysis_results
207
 
208
- # Create static directory if it doesn't exist
209
- os.makedirs("static", exist_ok=True)
 
 
 
 
 
210
 
211
- # Create PDF filename and path
212
- filename = f"Report_{analysis_results['symbol']}_{datetime.now().strftime('%d%m%Y')}.pdf"
213
- pdf_path = os.path.join("static", filename)
214
-
215
- # Display information
216
- st.markdown("Get a complete PDF report with price charts:")
217
 
218
- # Import PDF generation function
219
- from modules.analysis_pipeline import generate_pdf_report
 
 
 
 
 
 
220
 
221
- # Generate and download PDF button (combined)
222
- if st.button("📊 Generate & Download PDF Report", use_container_width=True, key="pdf_btn", type="primary"):
223
- # Check if file doesn't exist or needs to be recreated
224
- if not os.path.exists(pdf_path):
225
- with st.spinner("Creating PDF report with charts..."):
226
- generate_pdf_report(analysis_results, pdf_path)
227
-
228
- if not os.path.exists(pdf_path):
229
- st.error("Failed to create PDF report.")
230
- st.stop()
231
 
232
- # Read PDF file for download
233
- with open(pdf_path, "rb") as pdf_file:
234
- pdf_bytes = pdf_file.read()
235
 
236
- # Display success message and download widget
237
- st.success("PDF report generated successfully!")
238
 
239
- st.download_button(
240
- label="⬇️ Download Report",
241
- data=pdf_bytes,
242
- file_name=filename,
243
- mime="application/pdf",
244
- use_container_width=True,
245
- key="download_pdf_btn"
246
- )
247
-
248
- # Report display section
249
- with col1:
250
- # Check if there's an analysis request
251
- if "analysis_requested" in st.session_state and st.session_state.analysis_requested:
252
- symbol = st.session_state.stock_symbol
253
-
254
- with st.spinner(f"🔍 Collecting data and analyzing {symbol} stock... (this may take a few minutes)"):
255
- try:
256
- # Run analysis
257
- analysis_results = asyncio.run(run_analysis_pipeline(symbol))
 
258
 
259
- # Save results to session state
260
- st.session_state.analysis_results = analysis_results
261
- st.session_state.analysis_complete = True
262
- st.session_state.analysis_requested = False
263
 
264
- # Automatically rerun to display results
265
- st.rerun()
266
- except Exception as e:
267
- st.error(f"An error occurred during analysis: {str(e)}")
268
- st.session_state.analysis_requested = False
269
-
270
- # Check if analysis is complete
271
- if "analysis_complete" in st.session_state and st.session_state.analysis_complete:
272
- # Get results from session state
273
- analysis_results = st.session_state.analysis_results
274
-
275
- # Create tabs to display content
276
- tab1, tab2, tab3, tab4, tab5 = st.tabs([
277
- "📋 Overview",
278
- "💰 Financial Health",
279
- "📰 News & Sentiment",
280
- "👨‍💼 Market Analysis",
281
- "📊 Price Charts"
282
- ])
283
-
284
- with tab1:
285
- # Display basic company information
286
- overview = analysis_results.get('overview', {})
287
- if overview:
288
- col1, col2 = st.columns([1, 1])
289
- with col1:
290
- st.subheader(f"{analysis_results['symbol']} - {overview.get('Name', 'N/A')}")
291
- st.write(f"**Industry:** {overview.get('Industry', 'N/A')}")
292
- st.write(f"**Sector:** {overview.get('Sector', 'N/A')}")
293
- with col2:
294
- st.write(f"**Market Cap:** {overview.get('MarketCapitalization', 'N/A')}")
295
- st.write(f"**P/E Ratio:** {overview.get('PERatio', 'N/A')}")
296
- st.write(f"**Dividend Yield:** {overview.get('DividendYield', 'N/A')}%")
297
 
298
- # Display summary
299
- st.markdown("### Summary & Recommendation")
300
- st.markdown(analysis_results['analysis']['summary'])
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
- with tab2:
303
- st.markdown("### Financial Health Analysis")
304
- st.markdown(analysis_results['analysis']['financial_health'])
305
-
306
- with tab3:
307
- st.markdown("### News & Market Sentiment Analysis")
308
- st.markdown(analysis_results['analysis']['news_sentiment'])
309
-
310
- with tab4:
311
- st.markdown("### Market Analysis")
312
- st.markdown(analysis_results['analysis']['expert_opinion'])
313
-
314
- with tab5:
315
- st.markdown("### Stock Price Charts")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
- # Display charts from price data
318
- price_data = analysis_results.get('price_data', {})
319
- if price_data:
320
- period_tabs = st.tabs(['1 Month', '3 Months', '1 Year'])
 
 
321
 
322
- periods = ['1_month', '3_months', '1_year']
323
- for i, period in enumerate(periods):
324
- with period_tabs[i]:
325
- if period in price_data:
326
- chart = create_price_chart(price_data[period], period)
327
- if chart:
328
- st.altair_chart(chart, use_container_width=True)
 
 
 
 
 
 
 
329
  else:
330
- st.info(f"Insufficient data to display chart for {period} timeframe.")
331
- else:
332
- st.info(f"No chart data available for {period} timeframe.")
333
- else:
334
- st.info("No price chart data available for this stock.")
335
- else:
336
- # Display instructions when no analysis is present
337
- st.info("👈 Enter a stock symbol and click 'Generate Report' to begin.")
338
- st.markdown("""
339
- ### About Stock Analysis Reports
340
-
341
- The stock analysis report includes the following information:
342
-
343
- 1. **Overview & Investment Recommendation**: Summary of the company and general investment potential assessment.
344
- 2. **Financial Health Analysis**: Evaluation of financial metrics, revenue growth, and profitability.
345
- 3. **News & Market Sentiment Analysis**: Summary of notable news related to the company.
346
- 4. **Market Analysis**: Analysis of current stock performance and market trends.
347
- 5. **Price Charts**: Stock price charts for various timeframes.
348
-
349
- Reports are generated based on data from multiple sources and analyzed by AI.
350
- """)
351
-
352
- # Display popular stock symbols
353
- st.markdown("### Popular Stock Symbols")
354
-
355
- # Display list of popular stock symbols in grid
356
- # Only take first 12 to avoid cluttering the interface
357
- display_stocks = STOCK_SYMBOLS[:12]
358
-
359
- # Create grid with 4 columns
360
- cols = st.columns(4)
361
- for i, stock in enumerate(display_stocks):
362
- col = cols[i % 4]
363
- if col.button(f"{stock['symbol']} - {stock['name']}", key=f"pop_stock_{i}", use_container_width=True):
364
- st.session_state.stock_symbol = stock['symbol']
365
- st.session_state.analysis_requested = True
366
- st.rerun()
 
14
  from modules.analysis_pipeline import run_analysis_pipeline, generate_html_report
15
  from twelvedata_api import TwelveDataAPI
16
 
17
+ # Initialize session state for this page
18
+ if "stock_report_initialized" not in st.session_state:
19
+ st.session_state.stock_report_initialized = True
20
+ if "analysis_requested" not in st.session_state:
21
+ st.session_state.analysis_requested = False
22
+ if "analysis_complete" not in st.session_state:
23
+ st.session_state.analysis_complete = False
24
+
25
+ # Page setup - make sure this is consistent with Home.py
26
  st.set_page_config(
27
  page_title="Stock Analysis Report",
28
  page_icon="📊",
29
+ layout="wide",
30
+ initial_sidebar_state="expanded"
31
  )
32
 
33
+ # Clear the page for fresh rendering
34
+ main_container = st.container()
 
 
 
 
35
 
36
+ with main_container:
37
+ # Application title
38
+ st.title("📄 In-depth Stock Analysis Report")
39
+ st.markdown("""
40
+ This application generates a comprehensive analysis report for a stock symbol, combining data from multiple sources
41
+ and using AI to synthesize information, helping you make better investment decisions.
42
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ # Function to create price chart
45
+ def create_price_chart(price_data, period):
46
+ """Create price chart from data"""
47
+ if 'values' not in price_data:
48
+ return None
49
+
50
+ df = pd.DataFrame(price_data['values'])
51
+ if df.empty:
52
+ return None
53
+
54
+ df['datetime'] = pd.to_datetime(df['datetime'])
55
+ df['close'] = pd.to_numeric(df['close'])
56
+
57
+ # Determine chart title based on time period
58
+ title_map = {
59
+ '1_month': 'Stock price over the last month',
60
+ '3_months': 'Stock price over the last 3 months',
61
+ '1_year': 'Stock price over the last year'
62
+ }
63
+
64
+ # Create chart with Altair
65
+ chart = alt.Chart(df).mark_line().encode(
66
+ x=alt.X('datetime:T', title='Time'),
67
+ y=alt.Y('close:Q', title='Closing Price', scale=alt.Scale(zero=False)),
68
+ tooltip=[
69
+ alt.Tooltip('datetime:T', title='Date', format='%d/%m/%Y'),
70
+ alt.Tooltip('close:Q', title='Closing Price', format=',.2f'),
71
+ alt.Tooltip('volume:Q', title='Volume', format=',.0f')
72
+ ]
73
+ ).properties(
74
+ title=title_map.get(period, f'Stock price ({period})'),
75
+ height=350
76
+ ).interactive()
77
+
78
+ return chart
79
 
80
+ # Function to convert analysis results to PDF
81
+ def convert_html_to_pdf(html_content):
82
+ """Convert HTML to PDF file"""
83
+ with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as f:
84
+ f.write(html_content.encode())
85
+ temp_html = f.name
86
+
87
+ pdf_bytes = weasyprint.HTML(filename=temp_html).write_pdf()
88
+
89
+ # Delete temporary file after use
90
+ os.unlink(temp_html)
91
+
92
+ return pdf_bytes
93
+
94
+ # Function to create PDF download link
95
+ def get_download_link(pdf_bytes, filename):
96
+ """Create download link for PDF file"""
97
+ b64 = base64.b64encode(pdf_bytes).decode()
98
+ href = f'<a href="data:application/pdf;base64,{b64}" download="{filename}">Download Report (PDF)</a>'
99
+ return href
100
 
101
+ # List of popular stock symbols and information
102
+ @st.cache_data(ttl=3600)
103
+ def load_stock_symbols():
104
+ """Load stock symbols from cache or create new cache"""
105
+ cache_file = "static/stock_symbols_cache.json"
106
+
107
+ # Check if cache exists
108
+ if os.path.exists(cache_file):
109
+ try:
110
+ with open(cache_file, 'r') as f:
111
+ return json.load(f)
112
+ except Exception as e:
113
+ print(f"Error loading cache: {e}")
114
+
115
+ # Default list if cache doesn't exist or fails to load
116
+ default_symbols = [
117
+ {"symbol": "AAPL", "name": "Apple Inc."},
118
+ {"symbol": "MSFT", "name": "Microsoft Corporation"},
119
+ {"symbol": "GOOGL", "name": "Alphabet Inc."},
120
+ {"symbol": "AMZN", "name": "Amazon.com Inc."},
121
+ {"symbol": "TSLA", "name": "Tesla, Inc."},
122
+ {"symbol": "META", "name": "Meta Platforms, Inc."},
123
+ {"symbol": "NVDA", "name": "NVIDIA Corporation"},
124
+ {"symbol": "JPM", "name": "JPMorgan Chase & Co."},
125
+ {"symbol": "V", "name": "Visa Inc."},
126
+ {"symbol": "JNJ", "name": "Johnson & Johnson"},
127
+ {"symbol": "WMT", "name": "Walmart Inc."},
128
+ {"symbol": "MA", "name": "Mastercard Incorporated"},
129
+ {"symbol": "PG", "name": "Procter & Gamble Co."},
130
+ {"symbol": "UNH", "name": "UnitedHealth Group Inc."},
131
+ {"symbol": "HD", "name": "Home Depot Inc."},
132
+ {"symbol": "BAC", "name": "Bank of America Corp."},
133
+ {"symbol": "XOM", "name": "Exxon Mobil Corporation"},
134
+ {"symbol": "DIS", "name": "Walt Disney Co."},
135
+ {"symbol": "CSCO", "name": "Cisco Systems, Inc."},
136
+ {"symbol": "VZ", "name": "Verizon Communications Inc."},
137
+ {"symbol": "ADBE", "name": "Adobe Inc."},
138
+ {"symbol": "NFLX", "name": "Netflix, Inc."},
139
+ {"symbol": "CMCSA", "name": "Comcast Corporation"},
140
+ {"symbol": "PFE", "name": "Pfizer Inc."},
141
+ {"symbol": "KO", "name": "Coca-Cola Company"},
142
+ {"symbol": "INTC", "name": "Intel Corporation"},
143
+ {"symbol": "PYPL", "name": "PayPal Holdings, Inc."},
144
+ {"symbol": "T", "name": "AT&T Inc."},
145
+ {"symbol": "PEP", "name": "PepsiCo, Inc."},
146
+ {"symbol": "MRK", "name": "Merck & Co., Inc."}
147
+ ]
148
+
149
+ # Try to fetch more comprehensive list if API key is available
150
  try:
151
+ from dotenv import load_dotenv
152
+ load_dotenv()
153
+ api_key = os.getenv("TWELVEDATA_API_KEY")
154
+ if api_key:
155
+ td_api = TwelveDataAPI(api_key)
156
+ stocks_data = td_api.get_all_stocks(exchange="NASDAQ")
157
+ if stocks_data and 'data' in stocks_data:
158
+ # Convert to format we need and take first 1000 stocks
159
+ symbols = [{"symbol": stock["symbol"], "name": stock.get("name", "Unknown")}
160
+ for stock in stocks_data['data']]
161
+
162
+ # Save to cache
163
+ os.makedirs(os.path.dirname(cache_file), exist_ok=True)
164
+ with open(cache_file, 'w') as f:
165
+ json.dump(symbols, f)
166
+
167
+ return symbols
168
  except Exception as e:
169
+ print(f"Error fetching stock symbols from API: {e}")
170
+
171
+ # If everything fails, return default list
172
+ return default_symbols
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
+ # Load stock symbols
175
+ STOCK_SYMBOLS = load_stock_symbols()
176
 
177
+ # Function to format stock options for display
178
+ def format_stock_option(stock):
179
+ return f"{stock['symbol']} - {stock['name']}"
180
 
181
+ # Create interface
182
+ col1, col2 = st.columns([3, 1])
183
 
184
+ # Information input section
185
+ with col2:
186
+ st.subheader("Enter Information")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
+ # Create a list of formatted options and a mapping back to symbols
189
+ stock_options = [format_stock_option(stock) for stock in STOCK_SYMBOLS]
190
 
191
+ # Use selectbox with search functionality
192
+ selected_stock = st.selectbox(
193
+ "Select a stock symbol",
194
+ options=stock_options,
195
+ index=0 if stock_options else None,
196
+ placeholder="Search for a stock symbol...",
197
+ )
198
 
199
+ # Extract symbol from selection
200
+ if selected_stock:
201
+ stock_symbol = selected_stock.split(" - ")[0]
202
+ else:
203
+ stock_symbol = ""
 
204
 
205
+ if st.button("Generate Report", use_container_width=True, type="primary"):
206
+ if not stock_symbol:
207
+ st.error("Please select a stock symbol to continue.")
208
+ else:
209
+ # Save stock symbol to session state to maintain between runs
210
+ st.session_state.stock_symbol = stock_symbol
211
+ st.session_state.analysis_requested = True
212
+ st.rerun()
213
 
214
+ # PDF report generation section
215
+ if "analysis_complete" in st.session_state and st.session_state.analysis_complete:
216
+ st.divider()
217
+ st.subheader("PDF Report")
 
 
 
 
 
 
218
 
219
+ # Get results from session state
220
+ analysis_results = st.session_state.analysis_results
 
221
 
222
+ # Create static directory if it doesn't exist
223
+ os.makedirs("static", exist_ok=True)
224
 
225
+ # Create PDF filename and path
226
+ filename = f"Report_{analysis_results['symbol']}_{datetime.now().strftime('%d%m%Y')}.pdf"
227
+ pdf_path = os.path.join("static", filename)
228
+
229
+ # Display information
230
+ st.markdown("Get a complete PDF report with price charts:")
231
+
232
+ # Import PDF generation function
233
+ from modules.analysis_pipeline import generate_pdf_report
234
+
235
+ # Generate and download PDF button (combined)
236
+ if st.button("📊 Generate & Download PDF Report", use_container_width=True, key="pdf_btn", type="primary"):
237
+ # Check if file doesn't exist or needs to be recreated
238
+ if not os.path.exists(pdf_path):
239
+ with st.spinner("Creating PDF report with charts..."):
240
+ generate_pdf_report(analysis_results, pdf_path)
241
+
242
+ if not os.path.exists(pdf_path):
243
+ st.error("Failed to create PDF report.")
244
+ st.stop()
245
 
246
+ # Read PDF file for download
247
+ with open(pdf_path, "rb") as pdf_file:
248
+ pdf_bytes = pdf_file.read()
 
249
 
250
+ # Display success message and download widget
251
+ st.success("PDF report generated successfully!")
252
+
253
+ st.download_button(
254
+ label="⬇️ Download Report",
255
+ data=pdf_bytes,
256
+ file_name=filename,
257
+ mime="application/pdf",
258
+ use_container_width=True,
259
+ key="download_pdf_btn"
260
+ )
261
+
262
+ # Report display section
263
+ with col1:
264
+ # Check if there's an analysis request
265
+ if "analysis_requested" in st.session_state and st.session_state.analysis_requested:
266
+ symbol = st.session_state.stock_symbol
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
+ with st.spinner(f"🔍 Collecting data and analyzing {symbol} stock... (this may take a few minutes)"):
269
+ try:
270
+ # Run analysis
271
+ analysis_results = asyncio.run(run_analysis_pipeline(symbol))
272
+
273
+ # Save results to session state
274
+ st.session_state.analysis_results = analysis_results
275
+ st.session_state.analysis_complete = True
276
+ st.session_state.analysis_requested = False
277
+
278
+ # Automatically rerun to display results
279
+ st.rerun()
280
+ except Exception as e:
281
+ st.error(f"An error occurred during analysis: {str(e)}")
282
+ st.session_state.analysis_requested = False
283
 
284
+ # Check if analysis is complete
285
+ if "analysis_complete" in st.session_state and st.session_state.analysis_complete:
286
+ # Get results from session state
287
+ analysis_results = st.session_state.analysis_results
288
+
289
+ # Create tabs to display content
290
+ tab1, tab2, tab3, tab4, tab5 = st.tabs([
291
+ "📋 Overview",
292
+ "💰 Financial Health",
293
+ "📰 News & Sentiment",
294
+ "👨‍💼 Market Analysis",
295
+ "📊 Price Charts"
296
+ ])
297
+
298
+ with tab1:
299
+ # Display basic company information
300
+ overview = analysis_results.get('overview', {})
301
+ if overview:
302
+ col1, col2 = st.columns([1, 1])
303
+ with col1:
304
+ st.subheader(f"{analysis_results['symbol']} - {overview.get('Name', 'N/A')}")
305
+ st.write(f"**Industry:** {overview.get('Industry', 'N/A')}")
306
+ st.write(f"**Sector:** {overview.get('Sector', 'N/A')}")
307
+ with col2:
308
+ st.write(f"**Market Cap:** {overview.get('MarketCapitalization', 'N/A')}")
309
+ st.write(f"**P/E Ratio:** {overview.get('PERatio', 'N/A')}")
310
+ st.write(f"**Dividend Yield:** {overview.get('DividendYield', 'N/A')}%")
311
+
312
+ # Display summary
313
+ st.markdown("### Summary & Recommendation")
314
+ st.markdown(analysis_results['analysis']['summary'])
315
+
316
+ with tab2:
317
+ st.markdown("### Financial Health Analysis")
318
+ st.markdown(analysis_results['analysis']['financial_health'])
319
+
320
+ with tab3:
321
+ st.markdown("### News & Market Sentiment Analysis")
322
+ st.markdown(analysis_results['analysis']['news_sentiment'])
323
 
324
+ with tab4:
325
+ st.markdown("### Market Analysis")
326
+ st.markdown(analysis_results['analysis']['expert_opinion'])
327
+
328
+ with tab5:
329
+ st.markdown("### Stock Price Charts")
330
 
331
+ # Display charts from price data
332
+ price_data = analysis_results.get('price_data', {})
333
+ if price_data:
334
+ period_tabs = st.tabs(['1 Month', '3 Months', '1 Year'])
335
+
336
+ periods = ['1_month', '3_months', '1_year']
337
+ for i, period in enumerate(periods):
338
+ with period_tabs[i]:
339
+ if period in price_data:
340
+ chart = create_price_chart(price_data[period], period)
341
+ if chart:
342
+ st.altair_chart(chart, use_container_width=True)
343
+ else:
344
+ st.info(f"Insufficient data to display chart for {period} timeframe.")
345
  else:
346
+ st.info(f"No chart data available for {period} timeframe.")
347
+ else:
348
+ st.info("No price chart data available for this stock.")
349
+ else:
350
+ # Display instructions when no analysis is present
351
+ st.info("👈 Enter a stock symbol and click 'Generate Report' to begin.")
352
+ st.markdown("""
353
+ ### About Stock Analysis Reports
354
+
355
+ The stock analysis report includes the following information:
356
+
357
+ 1. **Overview & Investment Recommendation**: Summary of the company and general investment potential assessment.
358
+ 2. **Financial Health Analysis**: Evaluation of financial metrics, revenue growth, and profitability.
359
+ 3. **News & Market Sentiment Analysis**: Summary of notable news related to the company.
360
+ 4. **Market Analysis**: Analysis of current stock performance and market trends.
361
+ 5. **Price Charts**: Stock price charts for various timeframes.
362
+
363
+ Reports are generated based on data from multiple sources and analyzed by AI.
364
+ """)
365
+
366
+ # Display popular stock symbols
367
+ st.markdown("### Popular Stock Symbols")
368
+
369
+ # Display list of popular stock symbols in grid
370
+ # Only take first 12 to avoid cluttering the interface
371
+ display_stocks = STOCK_SYMBOLS[:12]
372
+
373
+ # Create grid with 4 columns
374
+ cols = st.columns(4)
375
+ for i, stock in enumerate(display_stocks):
376
+ col = cols[i % 4]
377
+ if col.button(f"{stock['symbol']} - {stock['name']}", key=f"pop_stock_{i}", use_container_width=True):
378
+ st.session_state.stock_symbol = stock['symbol']
379
+ st.session_state.analysis_requested = True
380
+ st.rerun()