Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,1998 +1,3 @@
|
|
1 |
-
# # import streamlit as st
|
2 |
-
# # import yfinance as yf
|
3 |
-
# # import pandas as pd
|
4 |
-
# # import plotly.graph_objects as go
|
5 |
-
# # from datetime import datetime, timedelta
|
6 |
-
# # from openai import OpenAI
|
7 |
-
# # import matplotlib.pyplot as plt
|
8 |
-
# # import seaborn as sns
|
9 |
-
# # import numpy as np
|
10 |
-
# # from ta.momentum import RSIIndicator
|
11 |
-
# # from ta.trend import MACD, SMAIndicator
|
12 |
-
# # from ta.volatility import BollingerBands
|
13 |
-
# # import os
|
14 |
-
# # # Initialize OpenAI client
|
15 |
-
# # client = OpenAI(api_key="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") # Replace with your actual OpenAI API key
|
16 |
-
# # # Verify your key is properly set
|
17 |
-
# # import openai
|
18 |
-
# # openai.api_key = os.getenv("OPENAI_API_KEY")
|
19 |
-
# # print(f"Key exists: {bool(openai.api_key)}")
|
20 |
-
# # # Page configuration
|
21 |
-
# # st.set_page_config(
|
22 |
-
# # page_title="AI Stock Market Analyst",
|
23 |
-
# # page_icon="π",
|
24 |
-
# # layout="wide",
|
25 |
-
# # initial_sidebar_state="expanded"
|
26 |
-
# # )
|
27 |
-
|
28 |
-
# # # Custom CSS for better styling
|
29 |
-
# # def local_css(file_name):
|
30 |
-
# # with open(file_name) as f:
|
31 |
-
# # st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
|
32 |
-
|
33 |
-
# # local_css("style.css")
|
34 |
-
|
35 |
-
# # # === Core Function: Get Live Stock Data ===
|
36 |
-
# # def get_live_data(ticker, period="1mo"):
|
37 |
-
# # """Fetch live stock data using yfinance with proper error handling"""
|
38 |
-
# # try:
|
39 |
-
# # stock = yf.Ticker(ticker)
|
40 |
-
# # hist = stock.history(period=period)
|
41 |
-
|
42 |
-
# # if hist.empty:
|
43 |
-
# # return None, f"β οΈ No data found for ticker {ticker}"
|
44 |
-
|
45 |
-
# # # Calculate technical indicators
|
46 |
-
# # # RSI
|
47 |
-
# # rsi_indicator = RSIIndicator(hist["Close"], window=14)
|
48 |
-
# # hist["RSI"] = rsi_indicator.rsi()
|
49 |
-
|
50 |
-
# # # Moving Averages
|
51 |
-
# # sma_20 = SMAIndicator(hist["Close"], window=20)
|
52 |
-
# # sma_50 = SMAIndicator(hist["Close"], window=50)
|
53 |
-
# # hist["MA_20"] = sma_20.sma_indicator()
|
54 |
-
# # hist["MA_50"] = sma_50.sma_indicator()
|
55 |
-
|
56 |
-
# # # MACD
|
57 |
-
# # macd = MACD(hist["Close"])
|
58 |
-
# # hist["MACD"] = macd.macd()
|
59 |
-
# # hist["MACD_Signal"] = macd.macd_signal()
|
60 |
-
# # hist["MACD_Hist"] = macd.macd_diff()
|
61 |
-
|
62 |
-
# # # Bollinger Bands
|
63 |
-
# # bb = BollingerBands(hist["Close"])
|
64 |
-
# # hist["BB_Upper"] = bb.bollinger_hband()
|
65 |
-
# # hist["BB_Lower"] = bb.bollinger_lband()
|
66 |
-
|
67 |
-
# # return hist, None
|
68 |
-
|
69 |
-
# # except Exception as e:
|
70 |
-
# # return None, f"β Error fetching data for {ticker}: {str(e)}"
|
71 |
-
|
72 |
-
# # # === Core Function: Stock Analysis with GPT-3.5 ===
|
73 |
-
# # def analyze_stock_with_gpt(query, ticker=None, data=None, error_msg=None):
|
74 |
-
# # """Get stock analysis from OpenAI GPT-3.5 with error handling"""
|
75 |
-
# # try:
|
76 |
-
# # # Prepare the prompt
|
77 |
-
# # prompt = f"""
|
78 |
-
# # You are a senior stock market analyst with 20 years of experience. Analyze the following query: '{query}'
|
79 |
-
|
80 |
-
# # {f"Ticker mentioned: {ticker}" if ticker else "No specific ticker mentioned"}
|
81 |
-
# # {error_msg if error_msg else ""}
|
82 |
-
|
83 |
-
# # Expected output format (use markdown with emojis for better readability):
|
84 |
-
|
85 |
-
# # ### π Analysis Report for {ticker if ticker else 'the stock'}
|
86 |
-
|
87 |
-
# # **1. Fundamental Overview**
|
88 |
-
# # - Current price: [if available]
|
89 |
-
# # - Key metrics: [if available]
|
90 |
-
|
91 |
-
# # **2. Technical Analysis**
|
92 |
-
# # - Trend analysis
|
93 |
-
# # - Key indicators (RSI, MACD, Moving Averages)
|
94 |
-
# # - Support/resistance levels
|
95 |
-
|
96 |
-
# # **3. Market Sentiment**
|
97 |
-
# # - Recent performance
|
98 |
-
# # - Volume analysis
|
99 |
-
|
100 |
-
# # **4. Recommendation**
|
101 |
-
# # - Clear buy/sell/hold recommendation with reasoning
|
102 |
-
# # - Price targets if possible
|
103 |
-
# # - Risk assessment
|
104 |
-
|
105 |
-
# # **5. Next Steps**
|
106 |
-
# # - What to watch for in coming days
|
107 |
-
|
108 |
-
# # {data.tail(10)[['Close', 'RSI', 'MA_20', 'MA_50', 'Volume']].to_string() if data is not None else 'No data available'}
|
109 |
-
|
110 |
-
# # IF DATA NOT AVALBLE YOU CAN GGIVE DAMMY DATA AS YOU OWN
|
111 |
-
|
112 |
-
# # """
|
113 |
-
|
114 |
-
# # response = client.chat.completions.create(
|
115 |
-
# # model="gpt-3.5-turbo",
|
116 |
-
# # messages=[
|
117 |
-
# # {"role": "system", "content": "You are a professional stock market analyst with expertise in technical and fundamental analysis. Provide detailed but concise analysis with clear recommendations.IF DATA WAS NOT AVLABLE SHOW ANY GRAPH ANY DATA GIVE DAMMY DATA TABLE FORMET "},
|
118 |
-
# # {"role": "user", "content": prompt}
|
119 |
-
# # ],
|
120 |
-
# # temperature=0.9
|
121 |
-
# # )
|
122 |
-
|
123 |
-
# # return response.choices[0].message.content
|
124 |
-
|
125 |
-
# # except Exception as e:
|
126 |
-
# # return f"β Error in analysis: {str(e)}"
|
127 |
-
|
128 |
-
# # # === Visualization Functions ===
|
129 |
-
# # def create_price_chart(data, ticker):
|
130 |
-
# # fig = go.Figure()
|
131 |
-
|
132 |
-
# # # Price line
|
133 |
-
# # fig.add_trace(go.Scatter(
|
134 |
-
# # x=data.index, y=data['Close'],
|
135 |
-
# # name='Price',
|
136 |
-
# # line=dict(color='#1f77b4', width=2)
|
137 |
-
# # ))
|
138 |
-
|
139 |
-
# # # Moving averages
|
140 |
-
# # fig.add_trace(go.Scatter(
|
141 |
-
# # x=data.index, y=data['MA_20'],
|
142 |
-
# # name='20-day MA',
|
143 |
-
# # line=dict(color='orange', width=1)
|
144 |
-
# # ))
|
145 |
-
|
146 |
-
# # fig.add_trace(go.Scatter(
|
147 |
-
# # x=data.index, y=data['MA_50'],
|
148 |
-
# # name='50-day MA',
|
149 |
-
# # line=dict(color='red', width=1)
|
150 |
-
# # ))
|
151 |
-
|
152 |
-
# # # Bollinger Bands
|
153 |
-
# # fig.add_trace(go.Scatter(
|
154 |
-
# # x=data.index, y=data['BB_Upper'],
|
155 |
-
# # name='Upper BB',
|
156 |
-
# # line=dict(color='gray', width=1, dash='dot')
|
157 |
-
# # ))
|
158 |
-
|
159 |
-
# # fig.add_trace(go.Scatter(
|
160 |
-
# # x=data.index, y=data['BB_Lower'],
|
161 |
-
# # name='Lower BB',
|
162 |
-
# # line=dict(color='gray', width=1, dash='dot'),
|
163 |
-
# # fill='tonexty',
|
164 |
-
# # fillcolor='rgba(200,200,200,0.2)'
|
165 |
-
# # ))
|
166 |
-
|
167 |
-
# # fig.update_layout(
|
168 |
-
# # title=f'{ticker} Price Analysis',
|
169 |
-
# # xaxis_title='Date',
|
170 |
-
# # yaxis_title='Price ($)',
|
171 |
-
# # hovermode='x unified',
|
172 |
-
# # template='plotly_white',
|
173 |
-
# # height=500
|
174 |
-
# # )
|
175 |
-
|
176 |
-
# # return fig
|
177 |
-
|
178 |
-
# # def create_rsi_chart(data, ticker):
|
179 |
-
# # fig = go.Figure()
|
180 |
-
|
181 |
-
# # # RSI line
|
182 |
-
# # fig.add_trace(go.Scatter(
|
183 |
-
# # x=data.index, y=data['RSI'],
|
184 |
-
# # name='RSI',
|
185 |
-
# # line=dict(color='purple', width=2)
|
186 |
-
# # ))
|
187 |
-
|
188 |
-
# # # Overbought/oversold lines
|
189 |
-
# # fig.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought")
|
190 |
-
# # fig.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold")
|
191 |
-
|
192 |
-
# # fig.update_layout(
|
193 |
-
# # title=f'{ticker} RSI (14-day)',
|
194 |
-
# # xaxis_title='Date',
|
195 |
-
# # yaxis_title='RSI',
|
196 |
-
# # hovermode='x unified',
|
197 |
-
# # template='plotly_white',
|
198 |
-
# # height=300
|
199 |
-
# # )
|
200 |
-
|
201 |
-
# # return fig
|
202 |
-
|
203 |
-
# # def create_macd_chart(data, ticker):
|
204 |
-
# # fig = go.Figure()
|
205 |
-
|
206 |
-
# # # MACD line
|
207 |
-
# # fig.add_trace(go.Scatter(
|
208 |
-
# # x=data.index, y=data['MACD'],
|
209 |
-
# # name='MACD',
|
210 |
-
# # line=dict(color='blue', width=2)
|
211 |
-
# # ))
|
212 |
-
|
213 |
-
# # # Signal line
|
214 |
-
# # fig.add_trace(go.Scatter(
|
215 |
-
# # x=data.index, y=data['MACD_Signal'],
|
216 |
-
# # name='Signal',
|
217 |
-
# # line=dict(color='orange', width=2)
|
218 |
-
# # ))
|
219 |
-
|
220 |
-
# # # Histogram
|
221 |
-
# # colors = np.where(data['MACD_Hist'] < 0, 'red', 'green')
|
222 |
-
# # fig.add_trace(go.Bar(
|
223 |
-
# # x=data.index, y=data['MACD_Hist'],
|
224 |
-
# # name='Histogram',
|
225 |
-
# # marker_color=colors,
|
226 |
-
# # opacity=0.6
|
227 |
-
# # ))
|
228 |
-
|
229 |
-
# # fig.update_layout(
|
230 |
-
# # title=f'{ticker} MACD',
|
231 |
-
# # xaxis_title='Date',
|
232 |
-
# # yaxis_title='Value',
|
233 |
-
# # hovermode='x unified',
|
234 |
-
# # template='plotly_white',
|
235 |
-
# # height=300
|
236 |
-
# # )
|
237 |
-
|
238 |
-
# # return fig
|
239 |
-
|
240 |
-
# # # === Streamlit UI ===
|
241 |
-
# # def main():
|
242 |
-
# # st.title("π AI Stock Market Analyst")
|
243 |
-
# # st.markdown("""
|
244 |
-
# # <style>
|
245 |
-
# # .big-font {
|
246 |
-
# # font-size:16px !important;
|
247 |
-
# # color: #4a4a4a;
|
248 |
-
# # }
|
249 |
-
# # </style>
|
250 |
-
# # """, unsafe_allow_html=True)
|
251 |
-
|
252 |
-
# # st.markdown('<p class="big-font">Get AI-powered stock analysis with live data, technical indicators, and investment recommendations</p>', unsafe_allow_html=True)
|
253 |
-
|
254 |
-
# # # Sidebar
|
255 |
-
# # with st.sidebar:
|
256 |
-
# # st.header("Settings")
|
257 |
-
# # default_ticker = st.text_input("Default Ticker", value="AAPL")
|
258 |
-
# # period_options = ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y"]
|
259 |
-
# # selected_period = st.selectbox("Analysis Period", period_options, index=2)
|
260 |
-
|
261 |
-
# # st.markdown("---")
|
262 |
-
# # st.markdown("### Technical Indicators")
|
263 |
-
# # show_rsi = st.checkbox("Show RSI", value=True)
|
264 |
-
# # show_macd = st.checkbox("Show MACD", value=True)
|
265 |
-
# # show_bb = st.checkbox("Show Bollinger Bands", value=True)
|
266 |
-
|
267 |
-
# # st.markdown("---")
|
268 |
-
# # st.markdown("""
|
269 |
-
# # **How to use:**
|
270 |
-
# # 1. Enter a stock ticker or ask a question
|
271 |
-
# # 2. View live data and analysis
|
272 |
-
# # 3. Get AI-powered recommendations
|
273 |
-
# # """)
|
274 |
-
|
275 |
-
# # # Main content
|
276 |
-
# # col1, col2 = st.columns([3, 1])
|
277 |
-
|
278 |
-
# # with col1:
|
279 |
-
# # query = st.text_input("Ask about a stock (e.g., 'Analyze AAPL' or 'Should I buy Tesla?')",
|
280 |
-
# # value=f"Analyze {default_ticker}")
|
281 |
-
|
282 |
-
# # with col2:
|
283 |
-
# # st.markdown("")
|
284 |
-
# # st.markdown("")
|
285 |
-
# # analyze_btn = st.button("Analyze", type="primary")
|
286 |
-
|
287 |
-
# # if analyze_btn or query:
|
288 |
-
# # # Attempt to extract ticker from query
|
289 |
-
# # words = query.upper().split()
|
290 |
-
# # possible_tickers = [word for word in words if word.isalpha() and len(word) <= 5]
|
291 |
-
# # ticker = possible_tickers[-1] if possible_tickers else default_ticker
|
292 |
-
|
293 |
-
# # st.markdown(f"### π {ticker} Analysis")
|
294 |
-
|
295 |
-
# # with st.spinner(f"Fetching data and analyzing {ticker}..."):
|
296 |
-
# # data, error_msg = get_live_data(ticker, selected_period)
|
297 |
-
|
298 |
-
# # if data is not None and not data.empty:
|
299 |
-
# # # Create tabs for different sections
|
300 |
-
# # tab1, tab2, tab3 = st.tabs(["π Price Analysis", "π Data", "π§ AI Analysis"])
|
301 |
-
|
302 |
-
# # with tab1:
|
303 |
-
# # # Price chart
|
304 |
-
# # st.plotly_chart(create_price_chart(data, ticker), use_container_width=True)
|
305 |
-
|
306 |
-
# # # Indicators in columns
|
307 |
-
# # col1, col2 = st.columns(2)
|
308 |
-
|
309 |
-
# # with col1:
|
310 |
-
# # if show_rsi:
|
311 |
-
# # st.plotly_chart(create_rsi_chart(data, ticker), use_container_width=True)
|
312 |
-
|
313 |
-
# # with col2:
|
314 |
-
# # if show_macd:
|
315 |
-
# # st.plotly_chart(create_macd_chart(data, ticker), use_container_width=True)
|
316 |
-
|
317 |
-
# # # Key metrics
|
318 |
-
# # st.subheader("Key Metrics")
|
319 |
-
# # last_close = data['Close'].iloc[-1]
|
320 |
-
# # last_rsi = data['RSI'].iloc[-1]
|
321 |
-
# # ma_20 = data['MA_20'].iloc[-1]
|
322 |
-
# # ma_50 = data['MA_50'].iloc[-1]
|
323 |
-
|
324 |
-
# # metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4)
|
325 |
-
# # metric_col1.metric("Current Price", f"${last_close:.2f}")
|
326 |
-
# # metric_col2.metric("RSI (14-day)", f"{last_rsi:.1f}",
|
327 |
-
# # "Overbought" if last_rsi > 70 else "Oversold" if last_rsi < 30 else "Neutral")
|
328 |
-
# # metric_col3.metric("20-day MA", f"${ma_20:.2f}",
|
329 |
-
# # f"{(last_close - ma_20):.2f} ({((last_close - ma_20)/ma_20*100):.2f}%)")
|
330 |
-
# # metric_col4.metric("50-day MA", f"${ma_50:.2f}",
|
331 |
-
# # f"{(last_close - ma_50):.2f} ({((last_close - ma_50)/ma_50*100):.2f}%)")
|
332 |
-
|
333 |
-
# # with tab2:
|
334 |
-
# # # Show recent data
|
335 |
-
# # st.subheader("Recent Market Data")
|
336 |
-
# # recent_data = data.tail(20)[['Open', 'High', 'Low', 'Close', 'Volume', 'RSI', 'MA_20', 'MA_50']]
|
337 |
-
# # recent_data.index = recent_data.index.strftime('%Y-%m-%d')
|
338 |
-
# # st.dataframe(recent_data.style.format({
|
339 |
-
# # 'Open': '{:.2f}',
|
340 |
-
# # 'High': '{:.2f}',
|
341 |
-
# # 'Low': '{:.2f}',
|
342 |
-
# # 'Close': '{:.2f}',
|
343 |
-
# # 'RSI': '{:.1f}',
|
344 |
-
# # 'MA_20': '{:.2f}',
|
345 |
-
# # 'MA_50': '{:.2f}'
|
346 |
-
# # }).background_gradient(cmap='Blues', subset=['RSI']),
|
347 |
-
# # use_container_width=True)
|
348 |
-
|
349 |
-
# # # Volume chart
|
350 |
-
# # st.subheader("Trading Volume")
|
351 |
-
# # fig_vol = go.Figure(go.Bar(
|
352 |
-
# # x=data.index, y=data['Volume'],
|
353 |
-
# # name='Volume',
|
354 |
-
# # marker_color='#1f77b4'
|
355 |
-
# # ))
|
356 |
-
# # fig_vol.update_layout(
|
357 |
-
# # height=300,
|
358 |
-
# # template='plotly_white'
|
359 |
-
# # )
|
360 |
-
# # st.plotly_chart(fig_vol, use_container_width=True)
|
361 |
-
|
362 |
-
# # with tab3:
|
363 |
-
# # # Get AI analysis
|
364 |
-
# # analysis = analyze_stock_with_gpt(query, ticker, data, error_msg)
|
365 |
-
# # st.markdown(analysis)
|
366 |
-
# # else:
|
367 |
-
# # st.error(error_msg if error_msg else "No data available for this ticker")
|
368 |
-
# # # Still try to get AI analysis
|
369 |
-
# # st.markdown("### π§ AI Analysis")
|
370 |
-
# # analysis = analyze_stock_with_gpt(query, ticker, None, error_msg)
|
371 |
-
# # st.markdown(analysis)
|
372 |
-
|
373 |
-
# # if __name__ == "__main__":
|
374 |
-
# # main()
|
375 |
-
|
376 |
-
|
377 |
-
# # import streamlit as st
|
378 |
-
# # import yfinance as yf
|
379 |
-
# # import pandas as pd
|
380 |
-
# # import plotly.graph_objects as go
|
381 |
-
# # from datetime import datetime, timedelta
|
382 |
-
# # from openai import OpenAI
|
383 |
-
# # import numpy as np
|
384 |
-
# # from ta.momentum import RSIIndicator
|
385 |
-
# # from ta.trend import MACD, SMAIndicator
|
386 |
-
# # from ta.volatility import BollingerBands
|
387 |
-
# # import os
|
388 |
-
# # import random
|
389 |
-
# # import time
|
390 |
-
# # from typing import Tuple, Optional
|
391 |
-
|
392 |
-
# # # Configuration
|
393 |
-
# # MAX_RETRIES = 3
|
394 |
-
# # RETRY_DELAY = 2
|
395 |
-
|
396 |
-
# # # Initialize OpenAI client with robust error handling
|
397 |
-
# # def init_openai_client():
|
398 |
-
# # """Initialize OpenAI client with retry logic"""
|
399 |
-
# # for attempt in range(MAX_RETRIES):
|
400 |
-
# # try:
|
401 |
-
# # api_key = os.getenv("OPENAI_API_KEY", "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")
|
402 |
-
# # if not api_key or api_key == "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":
|
403 |
-
# # raise ValueError("OpenAI API key not configured")
|
404 |
-
|
405 |
-
# # client = OpenAI(api_key=api_key)
|
406 |
-
# # # Test the connection
|
407 |
-
# # client.models.list()
|
408 |
-
# # return client
|
409 |
-
# # except Exception as e:
|
410 |
-
# # if attempt == MAX_RETRIES - 1:
|
411 |
-
# # st.error(f"Failed to initialize OpenAI after {MAX_RETRIES} attempts: {str(e)}")
|
412 |
-
# # return None
|
413 |
-
# # time.sleep(RETRY_DELAY)
|
414 |
-
|
415 |
-
# # client = init_openai_client()
|
416 |
-
|
417 |
-
# # # Page configuration
|
418 |
-
# # st.set_page_config(
|
419 |
-
# # page_title="π AI Stock Analyst Pro+",
|
420 |
-
# # page_icon="π",
|
421 |
-
# # layout="wide",
|
422 |
-
# # initial_sidebar_state="expanded"
|
423 |
-
# # )
|
424 |
-
|
425 |
-
# # # Custom CSS for modern styling
|
426 |
-
# # st.markdown("""
|
427 |
-
# # <style>
|
428 |
-
# # .stApp {
|
429 |
-
# # background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
430 |
-
# # }
|
431 |
-
# # .header {
|
432 |
-
# # color: #2c3e50;
|
433 |
-
# # text-shadow: 1px 1px 3px rgba(0,0,0,0.1);
|
434 |
-
# # }
|
435 |
-
# # .metric-box {
|
436 |
-
# # background: white;
|
437 |
-
# # border-radius: 10px;
|
438 |
-
# # padding: 15px;
|
439 |
-
# # box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
440 |
-
# # transition: all 0.3s ease;
|
441 |
-
# # }
|
442 |
-
# # .metric-box:hover {
|
443 |
-
# # transform: translateY(-3px);
|
444 |
-
# # box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
445 |
-
# # }
|
446 |
-
# # .stTabs [aria-selected="true"] {
|
447 |
-
# # background: linear-gradient(90deg, #3498db 0%, #2980b9 100%) !important;
|
448 |
-
# # color: white !important;
|
449 |
-
# # }
|
450 |
-
# # .stButton>button {
|
451 |
-
# # background: linear-gradient(90deg, #3498db 0%, #2980b9 100%);
|
452 |
-
# # color: white;
|
453 |
-
# # border: none;
|
454 |
-
# # border-radius: 8px;
|
455 |
-
# # padding: 10px 20px;
|
456 |
-
# # font-weight: 600;
|
457 |
-
# # }
|
458 |
-
# # .error-box {
|
459 |
-
# # background: #ffebee;
|
460 |
-
# # border-left: 4px solid #f44336;
|
461 |
-
# # padding: 1rem;
|
462 |
-
# # margin: 1rem 0;
|
463 |
-
# # }
|
464 |
-
# # .warning-box {
|
465 |
-
# # background: #fff8e1;
|
466 |
-
# # border-left: 4px solid #ffc107;
|
467 |
-
# # padding: 1rem;
|
468 |
-
# # margin: 1rem 0;
|
469 |
-
# # }
|
470 |
-
# # </style>
|
471 |
-
# # """, unsafe_allow_html=True)
|
472 |
-
|
473 |
-
# # # === Core Data Functions ===
|
474 |
-
# # def generate_dummy_data(ticker: str, days: int = 30) -> Tuple[pd.DataFrame, str]:
|
475 |
-
# # """Generate realistic dummy stock data with proper technical indicators"""
|
476 |
-
# # try:
|
477 |
-
# # dates = pd.date_range(end=datetime.today(), periods=days)
|
478 |
-
# # base_price = random.uniform(50, 200)
|
479 |
-
# # volatility = random.uniform(1.0, 3.0) # More realistic volatility
|
480 |
-
|
481 |
-
# # # Generate price series with momentum
|
482 |
-
# # prices = []
|
483 |
-
# # momentum = 0
|
484 |
-
# # for i in range(days):
|
485 |
-
# # # Add some momentum effect
|
486 |
-
# # if i > 5 and random.random() > 0.7:
|
487 |
-
# # momentum = random.uniform(-2, 2)
|
488 |
-
|
489 |
-
# # change = random.uniform(-volatility, volatility) + momentum
|
490 |
-
# # new_price = base_price * (1 + change/100)
|
491 |
-
|
492 |
-
# # # Ensure prices don't go negative
|
493 |
-
# # prices.append(max(1.0, new_price))
|
494 |
-
# # base_price = prices[-1]
|
495 |
-
|
496 |
-
# # # Create DataFrame with realistic OHLC data
|
497 |
-
# # data = pd.DataFrame({
|
498 |
-
# # 'Open': prices,
|
499 |
-
# # 'High': [p * (1 + random.uniform(0, 0.02)) for p in prices],
|
500 |
-
# # 'Low': [p * (1 - random.uniform(0, 0.02)) for p in prices],
|
501 |
-
# # 'Close': prices,
|
502 |
-
# # 'Volume': [int(random.gauss(2000000, 500000)) for _ in prices]
|
503 |
-
# # }, index=dates)
|
504 |
-
|
505 |
-
# # # Calculate technical indicators
|
506 |
-
# # data = calculate_technical_indicators(data)
|
507 |
-
|
508 |
-
# # return data, f"β οΈ Using simulated data for {ticker}"
|
509 |
-
# # except Exception as e:
|
510 |
-
# # st.error(f"Dummy data generation failed: {str(e)}")
|
511 |
-
# # # Fallback to very simple data
|
512 |
-
# # return pd.DataFrame({'Close': [100]}, index=[datetime.today()]), "β οΈ Basic fallback data"
|
513 |
-
|
514 |
-
# # def calculate_technical_indicators(data: pd.DataFrame) -> pd.DataFrame:
|
515 |
-
# # """Calculate all technical indicators for a dataframe"""
|
516 |
-
# # try:
|
517 |
-
# # # RSI
|
518 |
-
# # if len(data) >= 14:
|
519 |
-
# # data["RSI"] = RSIIndicator(data["Close"], window=14).rsi()
|
520 |
-
|
521 |
-
# # # Moving Averages
|
522 |
-
# # if len(data) >= 20:
|
523 |
-
# # data["MA_20"] = SMAIndicator(data["Close"], window=20).sma_indicator()
|
524 |
-
# # if len(data) >= 50:
|
525 |
-
# # data["MA_50"] = SMAIndicator(data["Close"], window=50).sma_indicator()
|
526 |
-
|
527 |
-
# # # MACD
|
528 |
-
# # if len(data) >= 26: # MACD minimum period
|
529 |
-
# # macd = MACD(data["Close"])
|
530 |
-
# # data["MACD"] = macd.macd()
|
531 |
-
# # data["MACD_Signal"] = macd.macd_signal()
|
532 |
-
# # data["MACD_Hist"] = macd.macd_diff()
|
533 |
-
|
534 |
-
# # # Bollinger Bands
|
535 |
-
# # if len(data) >= 20: # BB minimum period
|
536 |
-
# # bb = BollingerBands(data["Close"])
|
537 |
-
# # data["BB_Upper"] = bb.bollinger_hband()
|
538 |
-
# # data["BB_Lower"] = bb.bollinger_lband()
|
539 |
-
|
540 |
-
# # return data
|
541 |
-
# # except Exception as e:
|
542 |
-
# # st.error(f"Technical indicator calculation failed: {str(e)}")
|
543 |
-
# # return data
|
544 |
-
|
545 |
-
# # def get_live_data(ticker: str, period: str = "1mo") -> Tuple[pd.DataFrame, Optional[str]]:
|
546 |
-
# # """Fetch live stock data with retry logic and fallback"""
|
547 |
-
# # for attempt in range(MAX_RETRIES):
|
548 |
-
# # try:
|
549 |
-
# # stock = yf.Ticker(ticker)
|
550 |
-
# # hist = stock.history(period=period)
|
551 |
-
|
552 |
-
# # if hist.empty:
|
553 |
-
# # raise ValueError(f"No data found for {ticker}")
|
554 |
-
|
555 |
-
# # hist = calculate_technical_indicators(hist)
|
556 |
-
# # return hist, None
|
557 |
-
|
558 |
-
# # except Exception as e:
|
559 |
-
# # if attempt == MAX_RETRIES - 1:
|
560 |
-
# # st.warning(f"Live data fetch failed, using simulated data: {str(e)}")
|
561 |
-
# # return generate_dummy_data(ticker)
|
562 |
-
# # time.sleep(RETRY_DELAY)
|
563 |
-
|
564 |
-
# # # === AI Analysis Functions ===
|
565 |
-
# # def analyze_stock_with_gpt(query: str, ticker: Optional[str] = None,
|
566 |
-
# # data: Optional[pd.DataFrame] = None,
|
567 |
-
# # error_msg: Optional[str] = None) -> str:
|
568 |
-
# # """Robust stock analysis with multiple fallback options"""
|
569 |
-
# # # Fallback analysis template
|
570 |
-
# # fallback_analysis = f"""
|
571 |
-
# # ## π Manual Analysis Report for {ticker if ticker else 'the stock'}
|
572 |
-
|
573 |
-
# # **Market Overview**
|
574 |
-
# # - Current market conditions are showing typical volatility
|
575 |
-
# # - Sector performance appears mixed
|
576 |
-
|
577 |
-
# # **Technical Assessment**
|
578 |
-
# # - Key indicators suggest {random.choice(['neutral', 'slightly bullish', 'slightly bearish'])} momentum
|
579 |
-
# # - Support/resistance levels not available
|
580 |
-
|
581 |
-
# # **Recommendation**
|
582 |
-
# # - **Action**: {random.choice(['Hold', 'Consider buying on dips', 'Consider partial profit taking'])}
|
583 |
-
# # - **Risk Level**: Moderate
|
584 |
-
|
585 |
-
# # **Note**: This is a fallback analysis. {error_msg or 'AI service unavailable'}
|
586 |
-
# # """
|
587 |
-
|
588 |
-
# # if client is None:
|
589 |
-
# # return fallback_analysis
|
590 |
-
|
591 |
-
# # try:
|
592 |
-
# # # Prepare context for the AI
|
593 |
-
# # context = {
|
594 |
-
# # "query": query,
|
595 |
-
# # "ticker": ticker,
|
596 |
-
# # "error_msg": error_msg,
|
597 |
-
# # "data_stats": data.describe().to_dict() if data is not None else None,
|
598 |
-
# # "last_5_days": data.tail().to_dict() if data is not None else None
|
599 |
-
# # }
|
600 |
-
|
601 |
-
# # prompt = f"""
|
602 |
-
# # As a senior stock analyst, provide comprehensive analysis with:
|
603 |
-
# # 1. Market context
|
604 |
-
# # 2. Technical assessment
|
605 |
-
# # 3. Actionable recommendation
|
606 |
-
# # 4. Risk analysis
|
607 |
-
|
608 |
-
# # Context: {str(context)}
|
609 |
-
# # """
|
610 |
-
|
611 |
-
# # for attempt in range(MAX_RETRIES):
|
612 |
-
# # try:
|
613 |
-
# # response = client.chat.completions.create(
|
614 |
-
# # model="gpt-3.5-turbo",
|
615 |
-
# # messages=[
|
616 |
-
# # {"role": "system", "content": "You are a professional stock analyst. Provide clear, data-driven insights."},
|
617 |
-
# # {"role": "user", "content": prompt}
|
618 |
-
# # ],
|
619 |
-
# # temperature=0.6,
|
620 |
-
# # timeout=15
|
621 |
-
# # )
|
622 |
-
# # return response.choices[0].message.content
|
623 |
-
# # except Exception as e:
|
624 |
-
# # if attempt == MAX_RETRIES - 1:
|
625 |
-
# # st.warning(f"AI analysis failed after {MAX_RETRIES} attempts: {str(e)}")
|
626 |
-
# # return fallback_analysis
|
627 |
-
# # time.sleep(RETRY_DELAY)
|
628 |
-
|
629 |
-
# # except Exception as e:
|
630 |
-
# # st.error(f"Analysis system error: {str(e)}")
|
631 |
-
# # return fallback_analysis
|
632 |
-
|
633 |
-
# # # === Visualization Functions ===
|
634 |
-
# # def create_price_chart(data: pd.DataFrame, ticker: str, is_dummy: bool = False) -> go.Figure:
|
635 |
-
# # """Create interactive price chart with indicators"""
|
636 |
-
# # fig = go.Figure()
|
637 |
-
|
638 |
-
# # # Price line with conditional styling
|
639 |
-
# # line_color = '#FF6B6B' if is_dummy else '#3498db'
|
640 |
-
# # fig.add_trace(go.Scatter(
|
641 |
-
# # x=data.index, y=data['Close'],
|
642 |
-
# # name='Price',
|
643 |
-
# # line=dict(color=line_color, width=2),
|
644 |
-
# # hovertemplate='Date: %{x}<br>Price: $%{y:.2f}<extra></extra>'
|
645 |
-
# # ))
|
646 |
-
|
647 |
-
# # # Add available indicators
|
648 |
-
# # if 'MA_20' in data:
|
649 |
-
# # fig.add_trace(go.Scatter(
|
650 |
-
# # x=data.index, y=data['MA_20'],
|
651 |
-
# # name='20-day MA',
|
652 |
-
# # line=dict(color='#f39c12', width=1.5, dash='dash'),
|
653 |
-
# # hovertemplate='20-day MA: $%{y:.2f}<extra></extra>'
|
654 |
-
# # ))
|
655 |
-
|
656 |
-
# # if 'MA_50' in data:
|
657 |
-
# # fig.add_trace(go.Scatter(
|
658 |
-
# # x=data.index, y=data['MA_50'],
|
659 |
-
# # name='50-day MA',
|
660 |
-
# # line=dict(color='#e74c3c', width=1.5, dash='dash'),
|
661 |
-
# # hovertemplate='50-day MA: $%{y:.2f}<extra></extra>'
|
662 |
-
# # ))
|
663 |
-
|
664 |
-
# # if 'BB_Upper' in data and 'BB_Lower' in data:
|
665 |
-
# # fig.add_trace(go.Scatter(
|
666 |
-
# # x=data.index, y=data['BB_Upper'],
|
667 |
-
# # name='Upper BB',
|
668 |
-
# # line=dict(color='#95a5a6', width=1, dash='dot'),
|
669 |
-
# # opacity=0.7
|
670 |
-
# # ))
|
671 |
-
# # fig.add_trace(go.Scatter(
|
672 |
-
# # x=data.index, y=data['BB_Lower'],
|
673 |
-
# # name='Lower BB',
|
674 |
-
# # line=dict(color='#95a5a6', width=1, dash='dot'),
|
675 |
-
# # fill='tonexty',
|
676 |
-
# # fillcolor='rgba(200,200,200,0.2)',
|
677 |
-
# # opacity=0.7
|
678 |
-
# # ))
|
679 |
-
|
680 |
-
# # fig.update_layout(
|
681 |
-
# # title=f'{ticker} Price Analysis {"(Simulated)" if is_dummy else ""}',
|
682 |
-
# # xaxis_title='Date',
|
683 |
-
# # yaxis_title='Price ($)',
|
684 |
-
# # hovermode='x unified',
|
685 |
-
# # template='plotly_white',
|
686 |
-
# # height=500,
|
687 |
-
# # margin=dict(l=50, r=50, b=50, t=80),
|
688 |
-
# # legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
|
689 |
-
# # )
|
690 |
-
# # return fig
|
691 |
-
|
692 |
-
# # def create_rsi_chart(data: pd.DataFrame, ticker: str) -> go.Figure:
|
693 |
-
# # """Create RSI chart with overbought/oversold markers"""
|
694 |
-
# # fig = go.Figure()
|
695 |
-
|
696 |
-
# # fig.add_trace(go.Scatter(
|
697 |
-
# # x=data.index, y=data['RSI'],
|
698 |
-
# # name='RSI',
|
699 |
-
# # line=dict(color='#9b59b6', width=2),
|
700 |
-
# # hovertemplate='RSI: %{y:.1f}<extra></extra>'
|
701 |
-
# # ))
|
702 |
-
|
703 |
-
# # # Add overbought/oversold lines
|
704 |
-
# # fig.add_hline(y=70, line_dash="dash", line_color="#e74c3c", annotation_text="Overbought")
|
705 |
-
# # fig.add_hline(y=30, line_dash="dash", line_color="#2ecc71", annotation_text="Oversold")
|
706 |
-
|
707 |
-
# # fig.update_layout(
|
708 |
-
# # title=f'{ticker} RSI (14-day)',
|
709 |
-
# # xaxis_title='Date',
|
710 |
-
# # yaxis_title='RSI',
|
711 |
-
# # hovermode='x unified',
|
712 |
-
# # template='plotly_white',
|
713 |
-
# # height=300
|
714 |
-
# # )
|
715 |
-
# # return fig
|
716 |
-
|
717 |
-
# # # === Main Application ===
|
718 |
-
# # def main():
|
719 |
-
# # st.title("π AI Stock Analyst Pro+")
|
720 |
-
# # st.markdown("""
|
721 |
-
# # <div class="header">
|
722 |
-
# # Professional stock analysis with AI-powered insights and robust fallback systems
|
723 |
-
# # </div>
|
724 |
-
# # """, unsafe_allow_html=True)
|
725 |
-
|
726 |
-
# # # Sidebar controls
|
727 |
-
# # with st.sidebar:
|
728 |
-
# # st.header("βοΈ Analysis Settings")
|
729 |
-
# # default_ticker = st.text_input("Default Ticker", value="AAPL")
|
730 |
-
# # period_options = ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y"]
|
731 |
-
# # selected_period = st.selectbox("Time Period", period_options, index=2)
|
732 |
-
|
733 |
-
# # st.markdown("---")
|
734 |
-
# # st.markdown("### π Technical Indicators")
|
735 |
-
# # show_rsi = st.checkbox("Show RSI", value=True)
|
736 |
-
# # show_macd = st.checkbox("Show MACD", value=True)
|
737 |
-
# # show_bb = st.checkbox("Show Bollinger Bands", value=True)
|
738 |
-
|
739 |
-
# # st.markdown("---")
|
740 |
-
# # st.markdown("""
|
741 |
-
# # **π How to use:**
|
742 |
-
# # 1. Enter a stock ticker (e.g., AAPL, MSFT)
|
743 |
-
# # 2. View live or simulated data
|
744 |
-
# # 3. Get AI-powered insights
|
745 |
-
# # """)
|
746 |
-
|
747 |
-
# # # Main analysis flow
|
748 |
-
# # query = st.text_input("Enter stock ticker or question",
|
749 |
-
# # value=f"Analyze {default_ticker}",
|
750 |
-
# # help="Try: 'Analyze AAPL technicals' or 'Should I buy TSLA?'")
|
751 |
-
|
752 |
-
# # if st.button("Run Analysis", type="primary", use_container_width=True):
|
753 |
-
# # # Extract ticker from query
|
754 |
-
# # ticker = default_ticker
|
755 |
-
# # words = query.upper().split()
|
756 |
-
# # possible_tickers = [word for word in words if word.isalpha() and len(word) <= 5]
|
757 |
-
# # if possible_tickers:
|
758 |
-
# # ticker = possible_tickers[-1]
|
759 |
-
|
760 |
-
# # with st.spinner(f"π Analyzing {ticker} (this may take a moment)..."):
|
761 |
-
# # try:
|
762 |
-
# # # Get data with fallback to dummy data
|
763 |
-
# # data, error_msg = get_live_data(ticker, selected_period)
|
764 |
-
# # is_dummy = error_msg is not None
|
765 |
-
|
766 |
-
# # # Display status
|
767 |
-
# # if is_dummy:
|
768 |
-
# # st.warning(error_msg)
|
769 |
-
# # else:
|
770 |
-
# # st.success("β
Successfully fetched market data")
|
771 |
-
|
772 |
-
# # # Create tabs for different views
|
773 |
-
# # tab1, tab2, tab3 = st.tabs(["π Price Analysis", "π Data Table", "π§ AI Insights"])
|
774 |
-
|
775 |
-
# # with tab1:
|
776 |
-
# # # Price chart
|
777 |
-
# # st.plotly_chart(create_price_chart(data, ticker, is_dummy),
|
778 |
-
# # use_container_width=True)
|
779 |
-
|
780 |
-
# # # Indicators in columns
|
781 |
-
# # if show_rsi or show_macd:
|
782 |
-
# # col1, col2 = st.columns(2)
|
783 |
-
|
784 |
-
# # with col1:
|
785 |
-
# # if show_rsi and 'RSI' in data:
|
786 |
-
# # st.plotly_chart(create_rsi_chart(data, ticker),
|
787 |
-
# # use_container_width=True)
|
788 |
-
|
789 |
-
# # with col2:
|
790 |
-
# # if show_macd and 'MACD' in data:
|
791 |
-
# # st.plotly_chart(create_macd_chart(data, ticker),
|
792 |
-
# # use_container_width=True)
|
793 |
-
|
794 |
-
# # # Key metrics
|
795 |
-
# # st.subheader("π Key Metrics")
|
796 |
-
# # cols = st.columns(4)
|
797 |
-
|
798 |
-
# # with cols[0]:
|
799 |
-
# # st.markdown('<div class="metric-box">', unsafe_allow_html=True)
|
800 |
-
# # if 'Close' in data:
|
801 |
-
# # last_close = data['Close'].iloc[-1]
|
802 |
-
# # st.metric("Current Price", f"${last_close:.2f}")
|
803 |
-
# # else:
|
804 |
-
# # st.metric("Current Price", "N/A")
|
805 |
-
# # st.markdown('</div>', unsafe_allow_html=True)
|
806 |
-
|
807 |
-
# # with cols[1]:
|
808 |
-
# # st.markdown('<div class="metric-box">', unsafe_allow_html=True)
|
809 |
-
# # if 'RSI' in data:
|
810 |
-
# # last_rsi = data['RSI'].iloc[-1]
|
811 |
-
# # status = ("Overbought" if last_rsi > 70
|
812 |
-
# # else "Oversold" if last_rsi < 30
|
813 |
-
# # else "Neutral")
|
814 |
-
# # st.metric("RSI (14-day)", f"{last_rsi:.1f}", status)
|
815 |
-
# # else:
|
816 |
-
# # st.metric("RSI (14-day)", "N/A")
|
817 |
-
# # st.markdown('</div>', unsafe_allow_html=True)
|
818 |
-
|
819 |
-
# # with cols[2]:
|
820 |
-
# # st.markdown('<div class="metric-box">', unsafe_allow_html=True)
|
821 |
-
# # if 'MA_20' in data and 'Close' in data:
|
822 |
-
# # ma_20 = data['MA_20'].iloc[-1]
|
823 |
-
# # change = (data['Close'].iloc[-1] - ma_20)/ma_20*100
|
824 |
-
# # st.metric("20-day MA", f"${ma_20:.2f}", f"{change:.2f}%")
|
825 |
-
# # else:
|
826 |
-
# # st.metric("20-day MA", "N/A")
|
827 |
-
# # st.markdown('</div>', unsafe_allow_html=True)
|
828 |
-
|
829 |
-
# # with cols[3]:
|
830 |
-
# # st.markdown('<div class="metric-box">', unsafe_allow_html=True)
|
831 |
-
# # if 'MA_50' in data and 'Close' in data:
|
832 |
-
# # ma_50 = data['MA_50'].iloc[-1]
|
833 |
-
# # change = (data['Close'].iloc[-1] - ma_50)/ma_50*100
|
834 |
-
# # st.metric("50-day MA", f"${ma_50:.2f}", f"{change:.2f}%")
|
835 |
-
# # else:
|
836 |
-
# # st.metric("50-day MA", "N/A")
|
837 |
-
# # st.markdown('</div>', unsafe_allow_html=True)
|
838 |
-
|
839 |
-
# # with tab2:
|
840 |
-
# # # Data table view
|
841 |
-
# # st.subheader("π Market Data")
|
842 |
-
|
843 |
-
# # if not data.empty:
|
844 |
-
# # display_data = data.tail(20).copy()
|
845 |
-
# # display_data.index = display_data.index.strftime('%Y-%m-%d')
|
846 |
-
|
847 |
-
# # # Select available columns to display
|
848 |
-
# # available_cols = [col for col in ['Open', 'High', 'Low', 'Close', 'Volume',
|
849 |
-
# # 'RSI', 'MA_20', 'MA_50'] if col in data]
|
850 |
-
|
851 |
-
# # st.dataframe(
|
852 |
-
# # display_data[available_cols].style.format("{:.2f}"),
|
853 |
-
# # use_container_width=True,
|
854 |
-
# # height=600
|
855 |
-
# # )
|
856 |
-
# # else:
|
857 |
-
# # st.warning("No data available to display")
|
858 |
-
|
859 |
-
# # with tab3:
|
860 |
-
# # # AI analysis
|
861 |
-
# # st.subheader("π€ AI-Powered Analysis")
|
862 |
-
# # analysis = analyze_stock_with_gpt(query, ticker, data, error_msg)
|
863 |
-
# # st.markdown(analysis)
|
864 |
-
|
865 |
-
# # except Exception as e:
|
866 |
-
# # st.error(f"Analysis failed: {str(e)}")
|
867 |
-
# # st.markdown("""
|
868 |
-
# # <div class="error-box">
|
869 |
-
# # <h4>π¨ Critical Error</h4>
|
870 |
-
# # <p>The analysis system encountered an unexpected error. Please try again later.</p>
|
871 |
-
# # </div>
|
872 |
-
# # """, unsafe_allow_html=True)
|
873 |
-
|
874 |
-
# # if __name__ == "__main__":
|
875 |
-
# # main()
|
876 |
-
|
877 |
-
|
878 |
-
|
879 |
-
|
880 |
-
|
881 |
-
|
882 |
-
|
883 |
-
|
884 |
-
# import streamlit as st
|
885 |
-
# import yfinance as yf
|
886 |
-
# import pandas as pd
|
887 |
-
# import plotly.graph_objects as go
|
888 |
-
# from datetime import datetime, timedelta
|
889 |
-
# from openai import OpenAI
|
890 |
-
# import numpy as np
|
891 |
-
# from ta.momentum import RSIIndicator
|
892 |
-
# from ta.trend import MACD, SMAIndicator
|
893 |
-
# from ta.volatility import BollingerBands
|
894 |
-
# import os
|
895 |
-
# from dotenv import load_dotenv
|
896 |
-
# import random
|
897 |
-
# import time
|
898 |
-
# from typing import Tuple, Optional
|
899 |
-
|
900 |
-
# # Load environment variables
|
901 |
-
# load_dotenv()
|
902 |
-
|
903 |
-
# # Configuration
|
904 |
-
# MAX_RETRIES = 3
|
905 |
-
# RETRY_DELAY = 2
|
906 |
-
# CACHE_EXPIRY = 3600 # 1 hour cache
|
907 |
-
|
908 |
-
# # Initialize OpenAI client securely
|
909 |
-
# def init_openai_client():
|
910 |
-
# """Initialize OpenAI client with proper error handling"""
|
911 |
-
# api_key = os.getenv("OPENAI_API_KEY")
|
912 |
-
# if not api_key:
|
913 |
-
# st.error("OpenAI API key not found. Please create a .env file with your key.")
|
914 |
-
# return None
|
915 |
-
|
916 |
-
# try:
|
917 |
-
# client = OpenAI(api_key="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")
|
918 |
-
# # Test the connection
|
919 |
-
# client.models.list()
|
920 |
-
# return client
|
921 |
-
# except Exception as e:
|
922 |
-
# st.error(f"Failed to initialize OpenAI: {str(e)}")
|
923 |
-
# return None
|
924 |
-
|
925 |
-
# client = init_openai_client()
|
926 |
-
|
927 |
-
# # Page configuration
|
928 |
-
# st.set_page_config(
|
929 |
-
# page_title="π AI Stock Analyst Pro",
|
930 |
-
# page_icon="π",
|
931 |
-
# layout="wide",
|
932 |
-
# initial_sidebar_state="expanded"
|
933 |
-
# )
|
934 |
-
|
935 |
-
# # Custom CSS for modern styling
|
936 |
-
# st.markdown("""
|
937 |
-
# <style>
|
938 |
-
# .stApp {
|
939 |
-
# background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
940 |
-
# font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
941 |
-
# }
|
942 |
-
# .header-title {
|
943 |
-
# color: #2c3e50;
|
944 |
-
# text-shadow: 1px 1px 3px rgba(0,0,0,0.1);
|
945 |
-
# margin-bottom: 0.5rem;
|
946 |
-
# }
|
947 |
-
# .metric-card {
|
948 |
-
# background: white;
|
949 |
-
# border-radius: 10px;
|
950 |
-
# padding: 1rem;
|
951 |
-
# box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
952 |
-
# transition: all 0.3s ease;
|
953 |
-
# border-left: 4px solid #3498db;
|
954 |
-
# }
|
955 |
-
# .metric-card:hover {
|
956 |
-
# transform: translateY(-3px);
|
957 |
-
# box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
958 |
-
# }
|
959 |
-
# .stTabs [aria-selected="true"] {
|
960 |
-
# background: #3498db !important;
|
961 |
-
# color: white !important;
|
962 |
-
# font-weight: 600;
|
963 |
-
# }
|
964 |
-
# .stButton>button {
|
965 |
-
# background: linear-gradient(90deg, #3498db 0%, #2980b9 100%);
|
966 |
-
# color: white;
|
967 |
-
# border: none;
|
968 |
-
# border-radius: 8px;
|
969 |
-
# padding: 0.75rem 1.5rem;
|
970 |
-
# font-weight: 600;
|
971 |
-
# transition: all 0.3s;
|
972 |
-
# }
|
973 |
-
# .stButton>button:hover {
|
974 |
-
# transform: translateY(-2px);
|
975 |
-
# box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
976 |
-
# }
|
977 |
-
# .warning-banner {
|
978 |
-
# background-color: #fff3cd;
|
979 |
-
# color: #856404;
|
980 |
-
# padding: 0.75rem;
|
981 |
-
# border-radius: 4px;
|
982 |
-
# border-left: 4px solid #ffc107;
|
983 |
-
# margin-bottom: 1rem;
|
984 |
-
# }
|
985 |
-
# .error-banner {
|
986 |
-
# background-color: #f8d7da;
|
987 |
-
# color: #721c24;
|
988 |
-
# padding: 0.75rem;
|
989 |
-
# border-radius: 4px;
|
990 |
-
# border-left: 4px solid #dc3545;
|
991 |
-
# margin-bottom: 1rem;
|
992 |
-
# }
|
993 |
-
# </style>
|
994 |
-
# """, unsafe_allow_html=True)
|
995 |
-
|
996 |
-
# # === Core Data Functions ===
|
997 |
-
# def generate_dummy_data(ticker: str, days: int = 30) -> Tuple[pd.DataFrame, str]:
|
998 |
-
# """Generate realistic dummy stock data with technical indicators"""
|
999 |
-
# try:
|
1000 |
-
# dates = pd.date_range(end=datetime.today(), periods=days)
|
1001 |
-
# base_price = random.uniform(50, 200)
|
1002 |
-
# volatility = random.uniform(1.5, 4.0)
|
1003 |
-
|
1004 |
-
# prices = []
|
1005 |
-
# trend = random.choice([-1, 1]) * random.uniform(0.1, 0.3)
|
1006 |
-
|
1007 |
-
# for i in range(days):
|
1008 |
-
# # Add trend and random fluctuation
|
1009 |
-
# change = trend + random.uniform(-volatility, volatility)
|
1010 |
-
# new_price = base_price * (1 + change/100)
|
1011 |
-
# prices.append(max(1.0, new_price))
|
1012 |
-
# base_price = prices[-1]
|
1013 |
-
|
1014 |
-
# data = pd.DataFrame({
|
1015 |
-
# 'Open': [p * random.uniform(0.99, 1.01) for p in prices],
|
1016 |
-
# 'High': [p * random.uniform(1.00, 1.02) for p in prices],
|
1017 |
-
# 'Low': [p * random.uniform(0.98, 1.00) for p in prices],
|
1018 |
-
# 'Close': prices,
|
1019 |
-
# 'Volume': [int(abs(random.gauss(2500000, 1000000))) for _ in prices]
|
1020 |
-
# }, index=dates)
|
1021 |
-
|
1022 |
-
# # Calculate indicators
|
1023 |
-
# data = calculate_technical_indicators(data)
|
1024 |
-
|
1025 |
-
# return data, f"β οΈ Using simulated data for {ticker}"
|
1026 |
-
# except Exception as e:
|
1027 |
-
# st.error(f"Dummy data generation failed: {str(e)}")
|
1028 |
-
# return pd.DataFrame({'Close': [100]}, index=[datetime.today()]), "β οΈ Basic fallback data"
|
1029 |
-
|
1030 |
-
# def calculate_technical_indicators(data: pd.DataFrame) -> pd.DataFrame:
|
1031 |
-
# """Calculate all technical indicators for a dataframe"""
|
1032 |
-
# try:
|
1033 |
-
# # RSI (14-day)
|
1034 |
-
# if len(data) >= 14:
|
1035 |
-
# data["RSI"] = RSIIndicator(data["Close"], window=14).rsi()
|
1036 |
-
|
1037 |
-
# # Moving Averages
|
1038 |
-
# if len(data) >= 20:
|
1039 |
-
# data["MA_20"] = SMAIndicator(data["Close"], window=20).sma_indicator()
|
1040 |
-
# if len(data) >= 50:
|
1041 |
-
# data["MA_50"] = SMAIndicator(data["Close"], window=50).sma_indicator()
|
1042 |
-
|
1043 |
-
# # MACD
|
1044 |
-
# if len(data) >= 26:
|
1045 |
-
# macd = MACD(data["Close"])
|
1046 |
-
# data["MACD"] = macd.macd()
|
1047 |
-
# data["MACD_Signal"] = macd.macd_signal()
|
1048 |
-
# data["MACD_Hist"] = macd.macd_diff()
|
1049 |
-
|
1050 |
-
# # Bollinger Bands
|
1051 |
-
# if len(data) >= 20:
|
1052 |
-
# bb = BollingerBands(data["Close"])
|
1053 |
-
# data["BB_Upper"] = bb.bollinger_hband()
|
1054 |
-
# data["BB_Lower"] = bb.bollinger_lband()
|
1055 |
-
|
1056 |
-
# return data
|
1057 |
-
# except Exception as e:
|
1058 |
-
# st.error(f"Technical indicator calculation failed: {str(e)}")
|
1059 |
-
# return data
|
1060 |
-
|
1061 |
-
# def get_live_data(ticker: str, period: str = "1mo") -> Tuple[pd.DataFrame, Optional[str]]:
|
1062 |
-
# """Fetch live stock data with retry logic and fallback"""
|
1063 |
-
# for attempt in range(MAX_RETRIES):
|
1064 |
-
# try:
|
1065 |
-
# stock = yf.Ticker(ticker)
|
1066 |
-
# hist = stock.history(period=period)
|
1067 |
-
|
1068 |
-
# if hist.empty:
|
1069 |
-
# raise ValueError(f"No data found for {ticker}")
|
1070 |
-
|
1071 |
-
# hist = calculate_technical_indicators(hist)
|
1072 |
-
# return hist, None
|
1073 |
-
|
1074 |
-
# except Exception as e:
|
1075 |
-
# if attempt == MAX_RETRIES - 1:
|
1076 |
-
# st.warning(f"Live data fetch failed, using simulated data: {str(e)}")
|
1077 |
-
# return generate_dummy_data(ticker)
|
1078 |
-
# time.sleep(RETRY_DELAY)
|
1079 |
-
|
1080 |
-
# # === AI Analysis Functions ===
|
1081 |
-
# def analyze_stock_with_gpt(query: str, ticker: Optional[str] = None,
|
1082 |
-
# data: Optional[pd.DataFrame] = None,
|
1083 |
-
# error_msg: Optional[str] = None) -> str:
|
1084 |
-
# """Robust stock analysis with fallback options"""
|
1085 |
-
# fallback_analysis = f"""
|
1086 |
-
# ## π Manual Analysis Report for {ticker if ticker else 'this stock'}
|
1087 |
-
# "gived anbalize the stock"
|
1088 |
-
# **Market Overview**
|
1089 |
-
# - Current conditions show {random.choice(['moderate', 'high', 'low'])} volatility
|
1090 |
-
# - Sector appears {random.choice(['bullish', 'bearish', 'neutral'])}
|
1091 |
-
|
1092 |
-
# **Technical Assessment**
|
1093 |
-
# - Momentum indicators suggest {random.choice(['upward', 'downward', 'sideways'])} trend
|
1094 |
-
# - Volume patterns indicate {random.choice(['growing', 'declining', 'stable'])} interest
|
1095 |
-
|
1096 |
-
# **Recommendation**
|
1097 |
-
# - Action: {random.choice(['Hold', 'Buy on dips', 'Take profits'])}
|
1098 |
-
# - Risk: {random.choice(['Moderate', 'High', 'Low'])}
|
1099 |
-
|
1100 |
-
# *Note: {error_msg or 'AI service unavailable'}*
|
1101 |
-
# """
|
1102 |
-
|
1103 |
-
# if client is None:
|
1104 |
-
# return fallback_analysis
|
1105 |
-
|
1106 |
-
# try:
|
1107 |
-
# # Prepare context
|
1108 |
-
# context = {
|
1109 |
-
# "query": query,
|
1110 |
-
# "ticker": ticker,
|
1111 |
-
# "error_msg": error_msg,
|
1112 |
-
# "data_summary": data.describe().to_dict() if data is not None else None,
|
1113 |
-
# "recent_data": data.tail().to_dict() if data is not None else None
|
1114 |
-
# }
|
1115 |
-
|
1116 |
-
# prompt = f"""
|
1117 |
-
# As a senior financial analyst, provide:
|
1118 |
-
# 1. Market context summary
|
1119 |
-
# 2. Technical analysis (RSI, MACD, Moving Averages)
|
1120 |
-
# 3. Investment recommendation with rationale
|
1121 |
-
# 4. Risk assessment
|
1122 |
-
|
1123 |
-
# Context: {context}
|
1124 |
-
# """
|
1125 |
-
|
1126 |
-
# for attempt in range(MAX_RETRIES):
|
1127 |
-
# try:
|
1128 |
-
# response = client.chat.completions.create(
|
1129 |
-
# model="gpt-3.5-turbo",
|
1130 |
-
# messages=[
|
1131 |
-
# {"role": "system", "content": "You are a professional stock analyst. Provide clear, actionable insights."},
|
1132 |
-
# {"role": "user", "content": prompt}
|
1133 |
-
# ],
|
1134 |
-
# temperature=0.6,
|
1135 |
-
# timeout=15
|
1136 |
-
# )
|
1137 |
-
# return response.choices[0].message.content
|
1138 |
-
# except Exception as e:
|
1139 |
-
# if attempt == MAX_RETRIES - 1:
|
1140 |
-
# st.warning(f"AI analysis failed after retries: {str(e)}")
|
1141 |
-
# return fallback_analysis
|
1142 |
-
# time.sleep(RETRY_DELAY)
|
1143 |
-
|
1144 |
-
# except Exception as e:
|
1145 |
-
# st.error(f"Analysis system error: {str(e)}")
|
1146 |
-
# return fallback_analysis
|
1147 |
-
|
1148 |
-
# # === Visualization Functions ===
|
1149 |
-
# def create_price_chart(data: pd.DataFrame, ticker: str, is_dummy: bool = False) -> go.Figure:
|
1150 |
-
# """Create interactive price chart with indicators"""
|
1151 |
-
# fig = go.Figure()
|
1152 |
-
|
1153 |
-
# # Price line with conditional styling
|
1154 |
-
# line_color = '#FF6B6B' if is_dummy else '#3498db'
|
1155 |
-
# fig.add_trace(go.Scatter(
|
1156 |
-
# x=data.index, y=data['Close'],
|
1157 |
-
# name='Price',
|
1158 |
-
# line=dict(color=line_color, width=2),
|
1159 |
-
# hovertemplate='Date: %{x}<br>Price: $%{y:.2f}<extra></extra>'
|
1160 |
-
# ))
|
1161 |
-
|
1162 |
-
# # Add available indicators
|
1163 |
-
# if 'MA_20' in data:
|
1164 |
-
# fig.add_trace(go.Scatter(
|
1165 |
-
# x=data.index, y=data['MA_20'],
|
1166 |
-
# name='20-day MA',
|
1167 |
-
# line=dict(color='#f39c12', width=1.5, dash='dash')
|
1168 |
-
# ))
|
1169 |
-
|
1170 |
-
# if 'MA_50' in data:
|
1171 |
-
# fig.add_trace(go.Scatter(
|
1172 |
-
# x=data.index, y=data['MA_50'],
|
1173 |
-
# name='50-day MA',
|
1174 |
-
# line=dict(color='#e74c3c', width=1.5, dash='dash')
|
1175 |
-
# ))
|
1176 |
-
|
1177 |
-
# if all(col in data for col in ['BB_Upper', 'BB_Lower']):
|
1178 |
-
# fig.add_trace(go.Scatter(
|
1179 |
-
# x=data.index, y=data['BB_Upper'],
|
1180 |
-
# name='Upper Band',
|
1181 |
-
# line=dict(color='#95a5a6', width=1, dash='dot'),
|
1182 |
-
# opacity=0.7
|
1183 |
-
# ))
|
1184 |
-
# fig.add_trace(go.Scatter(
|
1185 |
-
# x=data.index, y=data['BB_Lower'],
|
1186 |
-
# name='Lower Band',
|
1187 |
-
# line=dict(color='#95a5a6', width=1, dash='dot'),
|
1188 |
-
# fill='tonexty',
|
1189 |
-
# fillcolor='rgba(200,200,200,0.2)',
|
1190 |
-
# opacity=0.7
|
1191 |
-
# ))
|
1192 |
-
|
1193 |
-
# fig.update_layout(
|
1194 |
-
# title=f'{ticker} Price Analysis {"(Simulated)" if is_dummy else ""}',
|
1195 |
-
# xaxis_title='Date',
|
1196 |
-
# yaxis_title='Price ($)',
|
1197 |
-
# hovermode='x unified',
|
1198 |
-
# template='plotly_white',
|
1199 |
-
# height=500,
|
1200 |
-
# margin=dict(l=50, r=50, b=50, t=80)
|
1201 |
-
# )
|
1202 |
-
# return fig
|
1203 |
-
# def create_rsi_chart(data: pd.DataFrame, ticker: str) -> go.Figure:
|
1204 |
-
# """Create RSI chart with overbought/oversold markers"""
|
1205 |
-
# fig = go.Figure()
|
1206 |
-
|
1207 |
-
# fig.add_trace(go.Scatter(
|
1208 |
-
# x=data.index, y=data['RSI'],
|
1209 |
-
# name='RSI',
|
1210 |
-
# line=dict(color='#9b59b6', width=2)
|
1211 |
-
# ))
|
1212 |
-
|
1213 |
-
# # Add threshold lines
|
1214 |
-
# fig.add_hline(y=70, line_dash="dash", line_color="#e74c3c", annotation_text="Overbought")
|
1215 |
-
# fig.add_hline(y=30, line_dash="dash", line_color="#2ecc71", annotation_text="Oversold")
|
1216 |
-
|
1217 |
-
# fig.update_layout(
|
1218 |
-
# title=f'{ticker} RSI (14-day)',
|
1219 |
-
# xaxis_title='Date',
|
1220 |
-
# yaxis_title='RSI Value',
|
1221 |
-
# hovermode='x unified',
|
1222 |
-
# template='plotly_white',
|
1223 |
-
# height=300
|
1224 |
-
# )
|
1225 |
-
# return fig
|
1226 |
-
|
1227 |
-
# # === Main Application ===
|
1228 |
-
# def main():
|
1229 |
-
# st.title("π AI Stock Analyst Pro")
|
1230 |
-
# st.markdown('<p class="header-title">Professional stock analysis with AI-powered insights</p>',
|
1231 |
-
# unsafe_allow_html=True)
|
1232 |
-
|
1233 |
-
# # Sidebar controls
|
1234 |
-
# with st.sidebar:
|
1235 |
-
# st.header("βοΈ Settings")
|
1236 |
-
# default_ticker = st.text_input("Default Ticker", value="AAPL")
|
1237 |
-
# period_options = ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y"]
|
1238 |
-
# selected_period = st.selectbox("Time Period", period_options, index=2)
|
1239 |
-
|
1240 |
-
# st.markdown("---")
|
1241 |
-
# st.markdown("### π Technical Indicators")
|
1242 |
-
# show_rsi = st.checkbox("Show RSI", value=True)
|
1243 |
-
# show_macd = st.checkbox("Show MACD", value=True)
|
1244 |
-
# show_bb = st.checkbox("Show Bollinger Bands", value=True)
|
1245 |
-
|
1246 |
-
# st.markdown("---")
|
1247 |
-
# st.markdown("""
|
1248 |
-
# **How to use:**
|
1249 |
-
# 1. Enter a stock ticker
|
1250 |
-
# 2. View live/simulated data
|
1251 |
-
# 3. Get AI-powered analysis
|
1252 |
-
# """)
|
1253 |
-
|
1254 |
-
# # Main analysis flow
|
1255 |
-
# query = st.text_input("Enter stock ticker or question",
|
1256 |
-
# value=f"Analyze {default_ticker}",
|
1257 |
-
# help="Example: 'Analyze AAPL' or 'Should I buy TSLA?'")
|
1258 |
-
|
1259 |
-
# if st.button("Run Analysis", type="primary"):
|
1260 |
-
# # Extract ticker from query
|
1261 |
-
# words = query.upper().split()
|
1262 |
-
# possible_tickers = [word for word in words if word.isalpha() and len(word) <= 5]
|
1263 |
-
# ticker = possible_tickers[-1] if possible_tickers else default_ticker
|
1264 |
-
|
1265 |
-
# with st.spinner(f"π Analyzing {ticker}..."):
|
1266 |
-
# try:
|
1267 |
-
# # Get data with fallback
|
1268 |
-
# data, error_msg = get_live_data(ticker, selected_period)
|
1269 |
-
# is_dummy = error_msg is not None
|
1270 |
-
|
1271 |
-
# # Display status
|
1272 |
-
# if is_dummy:
|
1273 |
-
# st.warning(error_msg)
|
1274 |
-
# else:
|
1275 |
-
# st.success("β
Fetched market data")
|
1276 |
-
|
1277 |
-
# # Create tabs
|
1278 |
-
# tab1, tab2, tab3 = st.tabs(["π Charts", "π Data", "π§ Analysis"])
|
1279 |
-
|
1280 |
-
# with tab1:
|
1281 |
-
# # Price chart
|
1282 |
-
# st.plotly_chart(create_price_chart(data, ticker, is_dummy),
|
1283 |
-
# use_container_width=True)
|
1284 |
-
|
1285 |
-
# # Indicators in columns
|
1286 |
-
# if show_rsi or show_macd:
|
1287 |
-
# col1, col2 = st.columns(2)
|
1288 |
-
|
1289 |
-
# with col1:
|
1290 |
-
# if show_rsi and 'RSI' in data:
|
1291 |
-
# st.plotly_chart(create_rsi_chart(data, ticker),
|
1292 |
-
# use_container_width=True)
|
1293 |
-
|
1294 |
-
# with col2:
|
1295 |
-
# if show_macd and 'MACD' in data:
|
1296 |
-
# st.plotly_chart(create_macd_chart(data, ticker),
|
1297 |
-
# use_container_width=True)
|
1298 |
-
|
1299 |
-
# # Key metrics
|
1300 |
-
# st.subheader("Key Metrics")
|
1301 |
-
# cols = st.columns(4)
|
1302 |
-
|
1303 |
-
# metric_data = [
|
1304 |
-
# ("Price", data['Close'].iloc[-1] if 'Close' in data else None, "${:.2f}"),
|
1305 |
-
# ("RSI", data['RSI'].iloc[-1] if 'RSI' in data else None, "{:.1f}"),
|
1306 |
-
# ("20-day MA", data['MA_20'].iloc[-1] if 'MA_20' in data else None, "${:.2f}"),
|
1307 |
-
# ("50-day MA", data['MA_50'].iloc[-1] if 'MA_50' in data else None, "${:.2f}")
|
1308 |
-
# ]
|
1309 |
-
|
1310 |
-
# for i, (title, value, fmt) in enumerate(metric_data):
|
1311 |
-
# with cols[i]:
|
1312 |
-
# st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
1313 |
-
# if value is not None:
|
1314 |
-
# delta = None
|
1315 |
-
# if title == "RSI":
|
1316 |
-
# status = ("Overbought" if value > 70
|
1317 |
-
# else "Oversold" if value < 30
|
1318 |
-
# else "Neutral")
|
1319 |
-
# st.metric(title, fmt.format(value), status)
|
1320 |
-
# elif "MA" in title:
|
1321 |
-
# price = data['Close'].iloc[-1]
|
1322 |
-
# ma_value = value
|
1323 |
-
# change = (price - ma_value)/ma_value*100
|
1324 |
-
# st.metric(title, fmt.format(ma_value), f"{change:.2f}%")
|
1325 |
-
# else:
|
1326 |
-
# st.metric(title, fmt.format(value))
|
1327 |
-
# else:
|
1328 |
-
# st.metric(title, "N/A")
|
1329 |
-
# st.markdown('</div>', unsafe_allow_html=True)
|
1330 |
-
|
1331 |
-
# with tab2:
|
1332 |
-
# st.subheader("Market Data")
|
1333 |
-
# if not data.empty:
|
1334 |
-
# display_data = data.tail(20).copy()
|
1335 |
-
# display_data.index = display_data.index.strftime('%Y-%m-%d')
|
1336 |
-
|
1337 |
-
# cols_to_show = [col for col in ['Open', 'High', 'Low', 'Close', 'Volume',
|
1338 |
-
# 'RSI', 'MA_20', 'MA_50'] if col in data]
|
1339 |
-
|
1340 |
-
# st.dataframe(
|
1341 |
-
# display_data[cols_to_show].style.format("{:.2f}"),
|
1342 |
-
# use_container_width=True,
|
1343 |
-
# height=600
|
1344 |
-
# )
|
1345 |
-
# else:
|
1346 |
-
# st.warning("No data available")
|
1347 |
-
|
1348 |
-
# with tab3:
|
1349 |
-
# st.subheader("AI Analysis")
|
1350 |
-
# analysis = analyze_stock_with_gpt(query, ticker, data, error_msg)
|
1351 |
-
# st.markdown(analysis)
|
1352 |
-
|
1353 |
-
# except Exception as e:
|
1354 |
-
# st.error(f"Analysis failed: {str(e)}")
|
1355 |
-
# st.markdown("""
|
1356 |
-
# <div class="error-banner">
|
1357 |
-
# <strong>Error:</strong> The analysis system encountered an unexpected problem.
|
1358 |
-
# </div>
|
1359 |
-
# """, unsafe_allow_html=True)
|
1360 |
-
|
1361 |
-
# if __name__ == "__main__":
|
1362 |
-
# if client: # Only run if OpenAI initialized successfully
|
1363 |
-
|
1364 |
-
|
1365 |
-
|
1366 |
-
|
1367 |
-
|
1368 |
-
|
1369 |
-
|
1370 |
-
|
1371 |
-
|
1372 |
-
|
1373 |
-
|
1374 |
-
|
1375 |
-
|
1376 |
-
|
1377 |
-
|
1378 |
-
|
1379 |
-
|
1380 |
-
|
1381 |
-
|
1382 |
-
|
1383 |
-
|
1384 |
-
|
1385 |
-
|
1386 |
-
|
1387 |
-
|
1388 |
-
|
1389 |
-
|
1390 |
-
|
1391 |
-
|
1392 |
-
|
1393 |
-
|
1394 |
-
|
1395 |
-
|
1396 |
-
|
1397 |
-
# import streamlit as st
|
1398 |
-
# import yfinance as yf
|
1399 |
-
# import pandas as pd
|
1400 |
-
# import plotly.graph_objects as go
|
1401 |
-
# from datetime import datetime, timedelta
|
1402 |
-
# from openai import OpenAI
|
1403 |
-
# import numpy as np
|
1404 |
-
# from ta.momentum import RSIIndicator
|
1405 |
-
# from ta.trend import MACD, SMAIndicator
|
1406 |
-
# from ta.volatility import BollingerBands
|
1407 |
-
# import os
|
1408 |
-
# from dotenv import load_dotenv
|
1409 |
-
# import random
|
1410 |
-
# import time
|
1411 |
-
# from typing import Tuple, Optional
|
1412 |
-
|
1413 |
-
# # Load environment variables
|
1414 |
-
# load_dotenv()
|
1415 |
-
|
1416 |
-
# # Configuration
|
1417 |
-
# MAX_RETRIES = 3
|
1418 |
-
# RETRY_DELAY = 2
|
1419 |
-
# CACHE_EXPIRY = 3600 # 1 hour cache
|
1420 |
-
|
1421 |
-
# # Initialize OpenAI client securely
|
1422 |
-
# def init_openai_client():
|
1423 |
-
# """Initialize OpenAI client with proper error handling"""
|
1424 |
-
# api_key = os.getenv("OPENAI_API_KEY")
|
1425 |
-
# if not api_key:
|
1426 |
-
# st.error("OpenAI API key not found. Please create a .env file with your key.")
|
1427 |
-
# return None
|
1428 |
-
|
1429 |
-
# try:
|
1430 |
-
# client = OpenAI(api_key="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")
|
1431 |
-
# # Test the connection
|
1432 |
-
# client.models.list()
|
1433 |
-
# return client
|
1434 |
-
# except Exception as e:
|
1435 |
-
# st.error(f"Failed to initialize OpenAI: {str(e)}")
|
1436 |
-
# return None
|
1437 |
-
|
1438 |
-
# client = init_openai_client()
|
1439 |
-
|
1440 |
-
# # Page configuration
|
1441 |
-
# st.set_page_config(
|
1442 |
-
# page_title="π AI Stock Analyst Pro",
|
1443 |
-
# page_icon="π",
|
1444 |
-
# layout="wide",
|
1445 |
-
# initial_sidebar_state="expanded"
|
1446 |
-
# )
|
1447 |
-
|
1448 |
-
# # Dark theme custom CSS
|
1449 |
-
# st.markdown("""
|
1450 |
-
# <style>
|
1451 |
-
# :root {
|
1452 |
-
# --primary: #3498db;
|
1453 |
-
# --background: #121212;
|
1454 |
-
# --secondary-bg: #1e1e1e;
|
1455 |
-
# --text: #ffffff;
|
1456 |
-
# --accent: #2980b9;
|
1457 |
-
# --warning: #ffc107;
|
1458 |
-
# --error: #dc3545;
|
1459 |
-
# }
|
1460 |
-
|
1461 |
-
# .stApp {
|
1462 |
-
# background-color: var(--background);
|
1463 |
-
# color: var(--text);
|
1464 |
-
# font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
1465 |
-
# }
|
1466 |
-
|
1467 |
-
# .header-title {
|
1468 |
-
# color: var(--primary);
|
1469 |
-
# text-shadow: 1px 1px 3px rgba(0,0,0,0.3);
|
1470 |
-
# margin-bottom: 0.5rem;
|
1471 |
-
# }
|
1472 |
-
|
1473 |
-
# .metric-card {
|
1474 |
-
# background-color: var(--secondary-bg);
|
1475 |
-
# border-radius: 10px;
|
1476 |
-
# padding: 1rem;
|
1477 |
-
# box-shadow: 0 4px 6px rgba(0,0,0,0.3);
|
1478 |
-
# transition: all 0.3s ease;
|
1479 |
-
# border-left: 4px solid var(--primary);
|
1480 |
-
# color: var(--text);
|
1481 |
-
# }
|
1482 |
-
|
1483 |
-
# .metric-card:hover {
|
1484 |
-
# transform: translateY(-3px);
|
1485 |
-
# box-shadow: 0 6px 12px rgba(0,0,0,0.4);
|
1486 |
-
# }
|
1487 |
-
|
1488 |
-
# .stTabs [aria-selected="true"] {
|
1489 |
-
# background: var(--primary) !important;
|
1490 |
-
# color: white !important;
|
1491 |
-
# font-weight: 600;
|
1492 |
-
# }
|
1493 |
-
|
1494 |
-
# .stButton>button {
|
1495 |
-
# background: linear-gradient(90deg, var(--primary) 0%, var(--accent) 100%);
|
1496 |
-
# color: white;
|
1497 |
-
# border: none;
|
1498 |
-
# border-radius: 8px;
|
1499 |
-
# padding: 0.75rem 1.5rem;
|
1500 |
-
# font-weight: 600;
|
1501 |
-
# transition: all 0.3s;
|
1502 |
-
# }
|
1503 |
-
|
1504 |
-
# .stButton>button:hover {
|
1505 |
-
# transform: translateY(-2px);
|
1506 |
-
# box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
1507 |
-
# }
|
1508 |
-
|
1509 |
-
# .warning-banner {
|
1510 |
-
# background-color: rgba(255, 193, 7, 0.2);
|
1511 |
-
# color: var(--warning);
|
1512 |
-
# padding: 0.75rem;
|
1513 |
-
# border-radius: 4px;
|
1514 |
-
# border-left: 4px solid var(--warning);
|
1515 |
-
# margin-bottom: 1rem;
|
1516 |
-
# }
|
1517 |
-
|
1518 |
-
# .error-banner {
|
1519 |
-
# background-color: rgba(220, 53, 69, 0.2);
|
1520 |
-
# color: var(--error);
|
1521 |
-
# padding: 0.75rem;
|
1522 |
-
# border-radius: 4px;
|
1523 |
-
# border-left: 4px solid var(--error);
|
1524 |
-
# margin-bottom: 1rem;
|
1525 |
-
# }
|
1526 |
-
|
1527 |
-
# /* Plotly dark theme */
|
1528 |
-
# .js-plotly-plot .plotly, .js-plotly-plot .plotly div {
|
1529 |
-
# background-color: var(--secondary-bg) !important;
|
1530 |
-
# color: var(--text) !important;
|
1531 |
-
# }
|
1532 |
-
|
1533 |
-
# .modebar {
|
1534 |
-
# background-color: var(--secondary-bg) !important;
|
1535 |
-
# }
|
1536 |
-
|
1537 |
-
# /* Text input styling */
|
1538 |
-
# .stTextInput>div>div>input {
|
1539 |
-
# background-color: var(--secondary-bg);
|
1540 |
-
# color: var(--text);
|
1541 |
-
# border: 1px solid #444;
|
1542 |
-
# }
|
1543 |
-
|
1544 |
-
# /* Select box styling */
|
1545 |
-
# .stSelectbox>div>div>select {
|
1546 |
-
# background-color: var(--secondary-bg);
|
1547 |
-
# color: var(--text);
|
1548 |
-
# border: 1px solid #444;
|
1549 |
-
# }
|
1550 |
-
|
1551 |
-
# /* Checkbox styling */
|
1552 |
-
# .stCheckbox>label {
|
1553 |
-
# color: var(--text);
|
1554 |
-
# }
|
1555 |
-
|
1556 |
-
# /* Dataframe styling */
|
1557 |
-
# .dataframe {
|
1558 |
-
# background-color: var(--secondary-bg) !important;
|
1559 |
-
# color: var(--text) !important;
|
1560 |
-
# }
|
1561 |
-
|
1562 |
-
# .stDataFrame {
|
1563 |
-
# background-color: var(--secondary-bg) !important;
|
1564 |
-
# }
|
1565 |
-
|
1566 |
-
# /* Sidebar styling */
|
1567 |
-
# .css-1d391kg, .css-1oe5cao {
|
1568 |
-
# background-color: var(--secondary-bg) !important;
|
1569 |
-
# }
|
1570 |
-
# </style>
|
1571 |
-
# """, unsafe_allow_html=True)
|
1572 |
-
|
1573 |
-
# # === Core Data Functions ===
|
1574 |
-
# def generate_dummy_data(ticker: str, days: int = 30) -> Tuple[pd.DataFrame, str]:
|
1575 |
-
# """Generate realistic dummy stock data with technical indicators"""
|
1576 |
-
# try:
|
1577 |
-
# dates = pd.date_range(end=datetime.today(), periods=days)
|
1578 |
-
# base_price = random.uniform(50, 200)
|
1579 |
-
# volatility = random.uniform(1.5, 4.0)
|
1580 |
-
|
1581 |
-
# prices = []
|
1582 |
-
# trend = random.choice([-1, 1]) * random.uniform(0.1, 0.3)
|
1583 |
-
|
1584 |
-
# for i in range(days):
|
1585 |
-
# # Add trend and random fluctuation
|
1586 |
-
# change = trend + random.uniform(-volatility, volatility)
|
1587 |
-
# new_price = base_price * (1 + change/100)
|
1588 |
-
# prices.append(max(1.0, new_price))
|
1589 |
-
# base_price = prices[-1]
|
1590 |
-
|
1591 |
-
# data = pd.DataFrame({
|
1592 |
-
# 'Open': [p * random.uniform(0.99, 1.01) for p in prices],
|
1593 |
-
# 'High': [p * random.uniform(1.00, 1.02) for p in prices],
|
1594 |
-
# 'Low': [p * random.uniform(0.98, 1.00) for p in prices],
|
1595 |
-
# 'Close': prices,
|
1596 |
-
# 'Volume': [int(abs(random.gauss(2500000, 1000000))) for _ in prices]
|
1597 |
-
# }, index=dates)
|
1598 |
-
|
1599 |
-
# # Calculate indicators
|
1600 |
-
# data = calculate_technical_indicators(data)
|
1601 |
-
|
1602 |
-
# return data, f"β οΈ Using simulated data for {ticker}"
|
1603 |
-
# except Exception as e:
|
1604 |
-
# st.error(f"Dummy data generation failed: {str(e)}")
|
1605 |
-
# return pd.DataFrame({'Close': [100]}, index=[datetime.today()]), "β οΈ Basic fallback data"
|
1606 |
-
|
1607 |
-
# def calculate_technical_indicators(data: pd.DataFrame) -> pd.DataFrame:
|
1608 |
-
# """Calculate all technical indicators for a dataframe"""
|
1609 |
-
# try:
|
1610 |
-
# # RSI (14-day)
|
1611 |
-
# if len(data) >= 14:
|
1612 |
-
# data["RSI"] = RSIIndicator(data["Close"], window=14).rsi()
|
1613 |
-
|
1614 |
-
# # Moving Averages
|
1615 |
-
# if len(data) >= 20:
|
1616 |
-
# data["MA_20"] = SMAIndicator(data["Close"], window=20).sma_indicator()
|
1617 |
-
# if len(data) >= 50:
|
1618 |
-
# data["MA_50"] = SMAIndicator(data["Close"], window=50).sma_indicator()
|
1619 |
-
|
1620 |
-
# # MACD
|
1621 |
-
# if len(data) >= 26:
|
1622 |
-
# macd = MACD(data["Close"])
|
1623 |
-
# data["MACD"] = macd.macd()
|
1624 |
-
# data["MACD_Signal"] = macd.macd_signal()
|
1625 |
-
# data["MACD_Hist"] = macd.macd_diff()
|
1626 |
-
|
1627 |
-
# # Bollinger Bands
|
1628 |
-
# if len(data) >= 20:
|
1629 |
-
# bb = BollingerBands(data["Close"])
|
1630 |
-
# data["BB_Upper"] = bb.bollinger_hband()
|
1631 |
-
# data["BB_Lower"] = bb.bollinger_lband()
|
1632 |
-
|
1633 |
-
# return data
|
1634 |
-
# except Exception as e:
|
1635 |
-
# st.error(f"Technical indicator calculation failed: {str(e)}")
|
1636 |
-
# return data
|
1637 |
-
|
1638 |
-
# def get_live_data(ticker: str, period: str = "1mo") -> Tuple[pd.DataFrame, Optional[str]]:
|
1639 |
-
# """Fetch live stock data with retry logic and fallback"""
|
1640 |
-
# for attempt in range(MAX_RETRIES):
|
1641 |
-
# try:
|
1642 |
-
# stock = yf.Ticker(ticker)
|
1643 |
-
# hist = stock.history(period=period)
|
1644 |
-
|
1645 |
-
# if hist.empty:
|
1646 |
-
# raise ValueError(f"No data found for {ticker}")
|
1647 |
-
|
1648 |
-
# hist = calculate_technical_indicators(hist)
|
1649 |
-
# return hist, None
|
1650 |
-
|
1651 |
-
# except Exception as e:
|
1652 |
-
# if attempt == MAX_RETRIES - 1:
|
1653 |
-
# st.warning(f"Live data fetch failed, using simulated data: {str(e)}")
|
1654 |
-
# return generate_dummy_data(ticker)
|
1655 |
-
# time.sleep(RETRY_DELAY)
|
1656 |
-
|
1657 |
-
# # === AI Analysis Functions ===
|
1658 |
-
# def analyze_stock_with_gpt(query: str, ticker: Optional[str] = None,
|
1659 |
-
# data: Optional[pd.DataFrame] = None,
|
1660 |
-
# error_msg: Optional[str] = None) -> str:
|
1661 |
-
# """Robust stock analysis with fallback options"""
|
1662 |
-
# fallback_analysis = f"""
|
1663 |
-
# ## π Manual Analysis Report for {ticker if ticker else 'this stock'}
|
1664 |
-
|
1665 |
-
# **Market Overview**
|
1666 |
-
# - Current conditions show {random.choice(['moderate', 'high', 'low'])} volatility
|
1667 |
-
# - Sector appears {random.choice(['bullish', 'bearish', 'neutral'])}
|
1668 |
-
|
1669 |
-
# **Technical Assessment**
|
1670 |
-
# - Momentum indicators suggest {random.choice(['upward', 'downward', 'sideways'])} trend
|
1671 |
-
# - Volume patterns indicate {random.choice(['growing', 'declining', 'stable'])} interest
|
1672 |
-
|
1673 |
-
# **Recommendation**
|
1674 |
-
# - Action: {random.choice(['Hold', 'Buy on dips', 'Take profits'])}
|
1675 |
-
# - Risk: {random.choice(['Moderate', 'High', 'Low'])}
|
1676 |
-
|
1677 |
-
# *Note: {error_msg or 'AI service unavailable'}*
|
1678 |
-
# """
|
1679 |
-
|
1680 |
-
# if client is None:
|
1681 |
-
# return fallback_analysis
|
1682 |
-
|
1683 |
-
# try:
|
1684 |
-
# # Prepare context
|
1685 |
-
# context = {
|
1686 |
-
# "query": query,
|
1687 |
-
# "ticker": ticker,
|
1688 |
-
# "error_msg": error_msg,
|
1689 |
-
# "data_summary": data.describe().to_dict() if data is not None else None,
|
1690 |
-
# "recent_data": data.tail().to_dict() if data is not None else None
|
1691 |
-
# }
|
1692 |
-
|
1693 |
-
# prompt = f"""
|
1694 |
-
# As a senior financial analyst, provide:
|
1695 |
-
# 1. Market context summary
|
1696 |
-
# 2. Technical analysis (RSI, MACD, Moving Averages)
|
1697 |
-
# 3. Investment recommendation with rationale
|
1698 |
-
# 4. Risk assessment
|
1699 |
-
|
1700 |
-
# Context: {context}
|
1701 |
-
# """
|
1702 |
-
|
1703 |
-
# for attempt in range(MAX_RETRIES):
|
1704 |
-
# try:
|
1705 |
-
# response = client.chat.completions.create(
|
1706 |
-
# model="gpt-3.5-turbo",
|
1707 |
-
# messages=[
|
1708 |
-
# {"role": "system", "content": "You are a professional stock analyst. Provide clear, actionable insights."},
|
1709 |
-
# {"role": "user", "content": prompt}
|
1710 |
-
# ],
|
1711 |
-
# temperature=0.6,
|
1712 |
-
# timeout=15
|
1713 |
-
# )
|
1714 |
-
# return response.choices[0].message.content
|
1715 |
-
# except Exception as e:
|
1716 |
-
# if attempt == MAX_RETRIES - 1:
|
1717 |
-
# st.warning(f"AI analysis failed after retries: {str(e)}")
|
1718 |
-
# return fallback_analysis
|
1719 |
-
# time.sleep(RETRY_DELAY)
|
1720 |
-
|
1721 |
-
# except Exception as e:
|
1722 |
-
# st.error(f"Analysis system error: {str(e)}")
|
1723 |
-
# return fallback_analysis
|
1724 |
-
|
1725 |
-
# # === Visualization Functions ===
|
1726 |
-
# def create_price_chart(data: pd.DataFrame, ticker: str, is_dummy: bool = False) -> go.Figure:
|
1727 |
-
# """Create interactive price chart with indicators"""
|
1728 |
-
# fig = go.Figure()
|
1729 |
-
|
1730 |
-
# # Price line with conditional styling
|
1731 |
-
# line_color = '#FF6B6B' if is_dummy else '#3498db'
|
1732 |
-
# fig.add_trace(go.Scatter(
|
1733 |
-
# x=data.index, y=data['Close'],
|
1734 |
-
# name='Price',
|
1735 |
-
# line=dict(color=line_color, width=2),
|
1736 |
-
# hovertemplate='Date: %{x}<br>Price: $%{y:.2f}<extra></extra>'
|
1737 |
-
# ))
|
1738 |
-
|
1739 |
-
# # Add available indicators
|
1740 |
-
# if 'MA_20' in data:
|
1741 |
-
# fig.add_trace(go.Scatter(
|
1742 |
-
# x=data.index, y=data['MA_20'],
|
1743 |
-
# name='20-day MA',
|
1744 |
-
# line=dict(color='#f39c12', width=1.5, dash='dash')
|
1745 |
-
# ))
|
1746 |
-
|
1747 |
-
# if 'MA_50' in data:
|
1748 |
-
# fig.add_trace(go.Scatter(
|
1749 |
-
# x=data.index, y=data['MA_50'],
|
1750 |
-
# name='50-day MA',
|
1751 |
-
# line=dict(color='#e74c3c', width=1.5, dash='dash')
|
1752 |
-
# ))
|
1753 |
-
|
1754 |
-
# if all(col in data for col in ['BB_Upper', 'BB_Lower']):
|
1755 |
-
# fig.add_trace(go.Scatter(
|
1756 |
-
# x=data.index, y=data['BB_Upper'],
|
1757 |
-
# name='Upper Band',
|
1758 |
-
# line=dict(color='#95a5a6', width=1, dash='dot'),
|
1759 |
-
# opacity=0.7
|
1760 |
-
# ))
|
1761 |
-
# fig.add_trace(go.Scatter(
|
1762 |
-
# x=data.index, y=data['BB_Lower'],
|
1763 |
-
# name='Lower Band',
|
1764 |
-
# line=dict(color='#95a5a6', width=1, dash='dot'),
|
1765 |
-
# fill='tonexty',
|
1766 |
-
# fillcolor='rgba(200,200,200,0.2)',
|
1767 |
-
# opacity=0.7
|
1768 |
-
# ))
|
1769 |
-
|
1770 |
-
# fig.update_layout(
|
1771 |
-
# title=f'{ticker} Price Analysis {"(Simulated)" if is_dummy else ""}',
|
1772 |
-
# xaxis_title='Date',
|
1773 |
-
# yaxis_title='Price ($)',
|
1774 |
-
# hovermode='x unified',
|
1775 |
-
# template='plotly_dark', # Changed to dark theme
|
1776 |
-
# height=500,
|
1777 |
-
# margin=dict(l=50, r=50, b=50, t=80),
|
1778 |
-
# plot_bgcolor='#1e1e1e',
|
1779 |
-
# paper_bgcolor='#121212',
|
1780 |
-
# font=dict(color='white')
|
1781 |
-
# )
|
1782 |
-
# return fig
|
1783 |
-
|
1784 |
-
# def create_rsi_chart(data: pd.DataFrame, ticker: str) -> go.Figure:
|
1785 |
-
# """Create RSI chart with overbought/oversold markers"""
|
1786 |
-
# fig = go.Figure()
|
1787 |
-
|
1788 |
-
# fig.add_trace(go.Scatter(
|
1789 |
-
# x=data.index, y=data['RSI'],
|
1790 |
-
# name='RSI',
|
1791 |
-
# line=dict(color='#9b59b6', width=2)
|
1792 |
-
# ))
|
1793 |
-
|
1794 |
-
# # Add threshold lines
|
1795 |
-
# fig.add_hline(y=70, line_dash="dash", line_color="#e74c3c", annotation_text="Overbought")
|
1796 |
-
# fig.add_hline(y=30, line_dash="dash", line_color="#2ecc71", annotation_text="Oversold")
|
1797 |
-
|
1798 |
-
# fig.update_layout(
|
1799 |
-
# title=f'{ticker} RSI (14-day)',
|
1800 |
-
# xaxis_title='Date',
|
1801 |
-
# yaxis_title='RSI Value',
|
1802 |
-
# hovermode='x unified',
|
1803 |
-
# template='plotly_dark', # Changed to dark theme
|
1804 |
-
# height=300,
|
1805 |
-
# plot_bgcolor='#1e1e1e',
|
1806 |
-
# paper_bgcolor='#121212',
|
1807 |
-
# font=dict(color='white')
|
1808 |
-
# )
|
1809 |
-
# return fig
|
1810 |
-
|
1811 |
-
# def create_macd_chart(data: pd.DataFrame, ticker: str) -> go.Figure:
|
1812 |
-
# """Create MACD chart"""
|
1813 |
-
# fig = go.Figure()
|
1814 |
-
|
1815 |
-
# if 'MACD' in data:
|
1816 |
-
# fig.add_trace(go.Scatter(
|
1817 |
-
# x=data.index, y=data['MACD'],
|
1818 |
-
# name='MACD',
|
1819 |
-
# line=dict(color='#3498db', width=2)
|
1820 |
-
# ))
|
1821 |
-
|
1822 |
-
# if 'MACD_Signal' in data:
|
1823 |
-
# fig.add_trace(go.Scatter(
|
1824 |
-
# x=data.index, y=data['MACD_Signal'],
|
1825 |
-
# name='Signal',
|
1826 |
-
# line=dict(color='#f39c12', width=2)
|
1827 |
-
# ))
|
1828 |
-
|
1829 |
-
# if 'MACD_Hist' in data:
|
1830 |
-
# colors = np.where(data['MACD_Hist'] < 0, '#e74c3c', '#2ecc71')
|
1831 |
-
# fig.add_trace(go.Bar(
|
1832 |
-
# x=data.index, y=data['MACD_Hist'],
|
1833 |
-
# name='Histogram',
|
1834 |
-
# marker_color=colors,
|
1835 |
-
# opacity=0.6
|
1836 |
-
# ))
|
1837 |
-
|
1838 |
-
# fig.update_layout(
|
1839 |
-
# title=f'{ticker} MACD',
|
1840 |
-
# xaxis_title='Date',
|
1841 |
-
# yaxis_title='Value',
|
1842 |
-
# hovermode='x unified',
|
1843 |
-
# template='plotly_dark', # Changed to dark theme
|
1844 |
-
# height=300,
|
1845 |
-
# plot_bgcolor='#1e1e1e',
|
1846 |
-
# paper_bgcolor='#121212',
|
1847 |
-
# font=dict(color='white')
|
1848 |
-
# )
|
1849 |
-
# return fig
|
1850 |
-
|
1851 |
-
# # === Main Application ===
|
1852 |
-
# def main():
|
1853 |
-
# st.title("π AI Stock Analyst Pro")
|
1854 |
-
# st.markdown('<p class="header-title">Professional stock analysis with AI-powered insights</p>',
|
1855 |
-
# unsafe_allow_html=True)
|
1856 |
-
|
1857 |
-
# # Sidebar controls
|
1858 |
-
# with st.sidebar:
|
1859 |
-
# st.header("βοΈ Settings")
|
1860 |
-
# default_ticker = st.text_input("Default Ticker", value="AAPL")
|
1861 |
-
# period_options = ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y"]
|
1862 |
-
# selected_period = st.selectbox("Time Period", period_options, index=2)
|
1863 |
-
|
1864 |
-
# st.markdown("---")
|
1865 |
-
# st.markdown("### π Technical Indicators")
|
1866 |
-
# show_rsi = st.checkbox("Show RSI", value=True)
|
1867 |
-
# show_macd = st.checkbox("Show MACD", value=True)
|
1868 |
-
# show_bb = st.checkbox("Show Bollinger Bands", value=True)
|
1869 |
-
|
1870 |
-
# st.markdown("---")
|
1871 |
-
# st.markdown("""
|
1872 |
-
# **How to use:**
|
1873 |
-
# 1. Enter a stock ticker
|
1874 |
-
# 2. View live/simulated data
|
1875 |
-
# 3. Get AI-powered analysis
|
1876 |
-
# """)
|
1877 |
-
|
1878 |
-
# # Main analysis flow
|
1879 |
-
# query = st.text_input("Enter stock ticker or question",
|
1880 |
-
# value=f"Analyze {default_ticker}",
|
1881 |
-
# help="Example: 'Analyze AAPL' or 'Should I buy TSLA?'")
|
1882 |
-
|
1883 |
-
# if st.button("Run Analysis", type="primary"):
|
1884 |
-
# # Extract ticker from query
|
1885 |
-
# words = query.upper().split()
|
1886 |
-
# possible_tickers = [word for word in words if word.isalpha() and len(word) <= 5]
|
1887 |
-
# ticker = possible_tickers[-1] if possible_tickers else default_ticker
|
1888 |
-
|
1889 |
-
# with st.spinner(f"π Analyzing {ticker}..."):
|
1890 |
-
# try:
|
1891 |
-
# # Get data with fallback
|
1892 |
-
# data, error_msg = get_live_data(ticker, selected_period)
|
1893 |
-
# is_dummy = error_msg is not None
|
1894 |
-
|
1895 |
-
# # Display status
|
1896 |
-
# if is_dummy:
|
1897 |
-
# st.warning(error_msg)
|
1898 |
-
# else:
|
1899 |
-
# st.success("β
Fetched market data")
|
1900 |
-
|
1901 |
-
# # Create tabs
|
1902 |
-
# tab1, tab2, tab3 = st.tabs(["π Charts", "π Data", "π§ Analysis"])
|
1903 |
-
|
1904 |
-
# with tab1:
|
1905 |
-
# # Price chart
|
1906 |
-
# st.plotly_chart(create_price_chart(data, ticker, is_dummy),
|
1907 |
-
# use_container_width=True)
|
1908 |
-
|
1909 |
-
# # Indicators in columns
|
1910 |
-
# if show_rsi or show_macd:
|
1911 |
-
# col1, col2 = st.columns(2)
|
1912 |
-
|
1913 |
-
# with col1:
|
1914 |
-
# if show_rsi and 'RSI' in data:
|
1915 |
-
# st.plotly_chart(create_rsi_chart(data, ticker),
|
1916 |
-
# use_container_width=True)
|
1917 |
-
|
1918 |
-
# with col2:
|
1919 |
-
# if show_macd and 'MACD' in data:
|
1920 |
-
# st.plotly_chart(create_macd_chart(data, ticker),
|
1921 |
-
# use_container_width=True)
|
1922 |
-
|
1923 |
-
# # Key metrics
|
1924 |
-
# st.subheader("Key Metrics")
|
1925 |
-
# cols = st.columns(4)
|
1926 |
-
|
1927 |
-
# metric_data = [
|
1928 |
-
# ("Price", data['Close'].iloc[-1] if 'Close' in data else None, "${:.2f}"),
|
1929 |
-
# ("RSI", data['RSI'].iloc[-1] if 'RSI' in data else None, "{:.1f}"),
|
1930 |
-
# ("20-day MA", data['MA_20'].iloc[-1] if 'MA_20' in data else None, "${:.2f}"),
|
1931 |
-
# ("50-day MA", data['MA_50'].iloc[-1] if 'MA_50' in data else None, "${:.2f}")
|
1932 |
-
# ]
|
1933 |
-
|
1934 |
-
# for i, (title, value, fmt) in enumerate(metric_data):
|
1935 |
-
# with cols[i]:
|
1936 |
-
# st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
1937 |
-
# if value is not None:
|
1938 |
-
# delta = None
|
1939 |
-
# if title == "RSI":
|
1940 |
-
# status = ("Overbought" if value > 70
|
1941 |
-
# else "Oversold" if value < 30
|
1942 |
-
# else "Neutral")
|
1943 |
-
# st.metric(title, fmt.format(value), status)
|
1944 |
-
# elif "MA" in title:
|
1945 |
-
# price = data['Close'].iloc[-1]
|
1946 |
-
# ma_value = value
|
1947 |
-
# change = (price - ma_value)/ma_value*100
|
1948 |
-
# st.metric(title, fmt.format(ma_value), f"{change:.2f}%")
|
1949 |
-
# else:
|
1950 |
-
# st.metric(title, fmt.format(value))
|
1951 |
-
# else:
|
1952 |
-
# st.metric(title, "N/A")
|
1953 |
-
# st.markdown('</div>', unsafe_allow_html=True)
|
1954 |
-
|
1955 |
-
# with tab2:
|
1956 |
-
# st.subheader("Market Data")
|
1957 |
-
# if not data.empty:
|
1958 |
-
# display_data = data.tail(20).copy()
|
1959 |
-
# display_data.index = display_data.index.strftime('%Y-%m-%d')
|
1960 |
-
|
1961 |
-
# cols_to_show = [col for col in ['Open', 'High', 'Low', 'Close', 'Volume',
|
1962 |
-
# 'RSI', 'MA_20', 'MA_50'] if col in data]
|
1963 |
-
|
1964 |
-
# st.dataframe(
|
1965 |
-
# display_data[cols_to_show].style.format("{:.2f}"),
|
1966 |
-
# use_container_width=True,
|
1967 |
-
# height=600
|
1968 |
-
# )
|
1969 |
-
# else:
|
1970 |
-
# st.warning("No data available")
|
1971 |
-
|
1972 |
-
# with tab3:
|
1973 |
-
# st.subheader("AI Analysis")
|
1974 |
-
# analysis = analyze_stock_with_gpt(query, ticker, data, error_msg)
|
1975 |
-
# st.markdown(analysis)
|
1976 |
-
|
1977 |
-
# except Exception as e:
|
1978 |
-
# st.error(f"Analysis failed: {str(e)}")
|
1979 |
-
# st.markdown("""
|
1980 |
-
# <div class="error-banner">
|
1981 |
-
# <strong>Error:</strong> The analysis system encountered an unexpected problem.
|
1982 |
-
# </div>
|
1983 |
-
# """, unsafe_allow_html=True)
|
1984 |
-
|
1985 |
-
# if __name__ == "__main__":
|
1986 |
-
# if client: # Only run if OpenAI initialized successfully
|
1987 |
-
# main()
|
1988 |
-
|
1989 |
-
|
1990 |
-
|
1991 |
-
|
1992 |
-
|
1993 |
-
|
1994 |
-
|
1995 |
-
|
1996 |
import streamlit as st
|
1997 |
import yfinance as yf
|
1998 |
import pandas as pd
|
@@ -2034,7 +39,7 @@ def init_gemini_client():
|
|
2034 |
|
2035 |
gemini_model = init_gemini_client()
|
2036 |
|
2037 |
-
# Page configuration
|
2038 |
st.set_page_config(
|
2039 |
page_title="π AI Stock Analyst Pro",
|
2040 |
page_icon="π",
|
@@ -2051,9 +56,88 @@ st.markdown("""
|
|
2051 |
|
2052 |
# === Core Data Functions ===
|
2053 |
# (Keep all your existing data functions exactly the same)
|
2054 |
-
|
2055 |
-
|
2056 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2057 |
|
2058 |
# === AI Analysis Functions ===
|
2059 |
def analyze_stock_with_gemini(query: str, ticker: Optional[str] = None,
|
@@ -2117,9 +201,130 @@ def analyze_stock_with_gemini(query: str, ticker: Optional[str] = None,
|
|
2117 |
|
2118 |
# === Visualization Functions ===
|
2119 |
# (Keep all your existing visualization functions exactly the same)
|
2120 |
-
|
2121 |
-
|
2122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2123 |
|
2124 |
# === Main Application ===
|
2125 |
def main():
|
@@ -2127,7 +332,7 @@ def main():
|
|
2127 |
st.markdown('<p class="header-title">Professional stock analysis with AI-powered insights</p>',
|
2128 |
unsafe_allow_html=True)
|
2129 |
|
2130 |
-
# Sidebar controls
|
2131 |
with st.sidebar:
|
2132 |
st.header("βοΈ Settings")
|
2133 |
default_ticker = st.text_input("Default Ticker", value="AAPL")
|
@@ -2161,7 +366,7 @@ def main():
|
|
2161 |
|
2162 |
with st.spinner(f"π Analyzing {ticker}..."):
|
2163 |
try:
|
2164 |
-
# Get data with fallback
|
2165 |
data, error_msg = get_live_data(ticker, selected_period)
|
2166 |
is_dummy = error_msg is not None
|
2167 |
|
@@ -2171,16 +376,76 @@ def main():
|
|
2171 |
else:
|
2172 |
st.success("β
Fetched market data")
|
2173 |
|
2174 |
-
# Create tabs
|
2175 |
tab1, tab2, tab3 = st.tabs(["π Charts", "π Data", "π§ Analysis"])
|
2176 |
|
2177 |
with tab1:
|
2178 |
-
#
|
2179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2180 |
|
2181 |
with tab2:
|
2182 |
-
|
2183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2184 |
|
2185 |
with tab3:
|
2186 |
st.subheader("AI Analysis")
|
@@ -2197,24 +462,4 @@ def main():
|
|
2197 |
|
2198 |
if __name__ == "__main__":
|
2199 |
if gemini_model: # Only run if Gemini initialized successfully
|
2200 |
-
main()
|
2201 |
-
|
2202 |
-
|
2203 |
-
|
2204 |
-
|
2205 |
-
|
2206 |
-
|
2207 |
-
|
2208 |
-
|
2209 |
-
|
2210 |
-
|
2211 |
-
|
2212 |
-
|
2213 |
-
|
2214 |
-
|
2215 |
-
|
2216 |
-
|
2217 |
-
|
2218 |
-
|
2219 |
-
|
2220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import yfinance as yf
|
3 |
import pandas as pd
|
|
|
39 |
|
40 |
gemini_model = init_gemini_client()
|
41 |
|
42 |
+
# Page configuration
|
43 |
st.set_page_config(
|
44 |
page_title="π AI Stock Analyst Pro",
|
45 |
page_icon="π",
|
|
|
56 |
|
57 |
# === Core Data Functions ===
|
58 |
# (Keep all your existing data functions exactly the same)
|
59 |
+
def generate_dummy_data(ticker: str, days: int = 30) -> Tuple[pd.DataFrame, str]:
|
60 |
+
"""Generate realistic dummy stock data with technical indicators"""
|
61 |
+
try:
|
62 |
+
dates = pd.date_range(end=datetime.today(), periods=days)
|
63 |
+
base_price = random.uniform(50, 200)
|
64 |
+
volatility = random.uniform(1.5, 4.0)
|
65 |
+
|
66 |
+
prices = []
|
67 |
+
trend = random.choice([-1, 1]) * random.uniform(0.1, 0.3)
|
68 |
+
|
69 |
+
for i in range(days):
|
70 |
+
# Add trend and random fluctuation
|
71 |
+
change = trend + random.uniform(-volatility, volatility)
|
72 |
+
new_price = base_price * (1 + change/100)
|
73 |
+
prices.append(max(1.0, new_price))
|
74 |
+
base_price = prices[-1]
|
75 |
+
|
76 |
+
data = pd.DataFrame({
|
77 |
+
'Open': [p * random.uniform(0.99, 1.01) for p in prices],
|
78 |
+
'High': [p * random.uniform(1.00, 1.02) for p in prices],
|
79 |
+
'Low': [p * random.uniform(0.98, 1.00) for p in prices],
|
80 |
+
'Close': prices,
|
81 |
+
'Volume': [int(abs(random.gauss(2500000, 1000000))) for _ in prices]
|
82 |
+
}, index=dates)
|
83 |
+
|
84 |
+
# Calculate indicators
|
85 |
+
data = calculate_technical_indicators(data)
|
86 |
+
|
87 |
+
return data, f"β οΈ Using simulated data for {ticker}"
|
88 |
+
except Exception as e:
|
89 |
+
st.error(f"Dummy data generation failed: {str(e)}")
|
90 |
+
return pd.DataFrame({'Close': [100]}, index=[datetime.today()]), "β οΈ Basic fallback data"
|
91 |
+
|
92 |
+
def calculate_technical_indicators(data: pd.DataFrame) -> pd.DataFrame:
|
93 |
+
"""Calculate all technical indicators for a dataframe"""
|
94 |
+
try:
|
95 |
+
# RSI (14-day)
|
96 |
+
if len(data) >= 14:
|
97 |
+
data["RSI"] = RSIIndicator(data["Close"], window=14).rsi()
|
98 |
+
|
99 |
+
# Moving Averages
|
100 |
+
if len(data) >= 20:
|
101 |
+
data["MA_20"] = SMAIndicator(data["Close"], window=20).sma_indicator()
|
102 |
+
if len(data) >= 50:
|
103 |
+
data["MA_50"] = SMAIndicator(data["Close"], window=50).sma_indicator()
|
104 |
+
|
105 |
+
# MACD
|
106 |
+
if len(data) >= 26:
|
107 |
+
macd = MACD(data["Close"])
|
108 |
+
data["MACD"] = macd.macd()
|
109 |
+
data["MACD_Signal"] = macd.macd_signal()
|
110 |
+
data["MACD_Hist"] = macd.macd_diff()
|
111 |
+
|
112 |
+
# Bollinger Bands
|
113 |
+
if len(data) >= 20:
|
114 |
+
bb = BollingerBands(data["Close"])
|
115 |
+
data["BB_Upper"] = bb.bollinger_hband()
|
116 |
+
data["BB_Lower"] = bb.bollinger_lband()
|
117 |
+
|
118 |
+
return data
|
119 |
+
except Exception as e:
|
120 |
+
st.error(f"Technical indicator calculation failed: {str(e)}")
|
121 |
+
return data
|
122 |
+
|
123 |
+
def get_live_data(ticker: str, period: str = "1mo") -> Tuple[pd.DataFrame, Optional[str]]:
|
124 |
+
"""Fetch live stock data with retry logic and fallback"""
|
125 |
+
for attempt in range(MAX_RETRIES):
|
126 |
+
try:
|
127 |
+
stock = yf.Ticker(ticker)
|
128 |
+
hist = stock.history(period=period)
|
129 |
+
|
130 |
+
if hist.empty:
|
131 |
+
raise ValueError(f"No data found for {ticker}")
|
132 |
+
|
133 |
+
hist = calculate_technical_indicators(hist)
|
134 |
+
return hist, None
|
135 |
+
|
136 |
+
except Exception as e:
|
137 |
+
if attempt == MAX_RETRIES - 1:
|
138 |
+
st.warning(f"Live data fetch failed, using simulated data: {str(e)}")
|
139 |
+
return generate_dummy_data(ticker)
|
140 |
+
time.sleep(RETRY_DELAY)
|
141 |
|
142 |
# === AI Analysis Functions ===
|
143 |
def analyze_stock_with_gemini(query: str, ticker: Optional[str] = None,
|
|
|
201 |
|
202 |
# === Visualization Functions ===
|
203 |
# (Keep all your existing visualization functions exactly the same)
|
204 |
+
def create_price_chart(data: pd.DataFrame, ticker: str, is_dummy: bool = False) -> go.Figure:
|
205 |
+
"""Create interactive price chart with indicators"""
|
206 |
+
fig = go.Figure()
|
207 |
+
|
208 |
+
# Price line with conditional styling
|
209 |
+
line_color = '#FF6B6B' if is_dummy else '#3498db'
|
210 |
+
fig.add_trace(go.Scatter(
|
211 |
+
x=data.index, y=data['Close'],
|
212 |
+
name='Price',
|
213 |
+
line=dict(color=line_color, width=2),
|
214 |
+
hovertemplate='Date: %{x}<br>Price: $%{y:.2f}<extra></extra>'
|
215 |
+
))
|
216 |
+
|
217 |
+
# Add available indicators
|
218 |
+
if 'MA_20' in data:
|
219 |
+
fig.add_trace(go.Scatter(
|
220 |
+
x=data.index, y=data['MA_20'],
|
221 |
+
name='20-day MA',
|
222 |
+
line=dict(color='#f39c12', width=1.5, dash='dash')
|
223 |
+
))
|
224 |
+
|
225 |
+
if 'MA_50' in data:
|
226 |
+
fig.add_trace(go.Scatter(
|
227 |
+
x=data.index, y=data['MA_50'],
|
228 |
+
name='50-day MA',
|
229 |
+
line=dict(color='#e74c3c', width=1.5, dash='dash')
|
230 |
+
))
|
231 |
+
|
232 |
+
if all(col in data for col in ['BB_Upper', 'BB_Lower']):
|
233 |
+
fig.add_trace(go.Scatter(
|
234 |
+
x=data.index, y=data['BB_Upper'],
|
235 |
+
name='Upper Band',
|
236 |
+
line=dict(color='#95a5a6', width=1, dash='dot'),
|
237 |
+
opacity=0.7
|
238 |
+
))
|
239 |
+
fig.add_trace(go.Scatter(
|
240 |
+
x=data.index, y=data['BB_Lower'],
|
241 |
+
name='Lower Band',
|
242 |
+
line=dict(color='#95a5a6', width=1, dash='dot'),
|
243 |
+
fill='tonexty',
|
244 |
+
fillcolor='rgba(200,200,200,0.2)',
|
245 |
+
opacity=0.7
|
246 |
+
))
|
247 |
+
|
248 |
+
fig.update_layout(
|
249 |
+
title=f'{ticker} Price Analysis {"(Simulated)" if is_dummy else ""}',
|
250 |
+
xaxis_title='Date',
|
251 |
+
yaxis_title='Price ($)',
|
252 |
+
hovermode='x unified',
|
253 |
+
template='plotly_dark',
|
254 |
+
height=500,
|
255 |
+
margin=dict(l=50, r=50, b=50, t=80),
|
256 |
+
plot_bgcolor='#1e1e1e',
|
257 |
+
paper_bgcolor='#121212',
|
258 |
+
font=dict(color='white')
|
259 |
+
)
|
260 |
+
return fig
|
261 |
+
|
262 |
+
def create_rsi_chart(data: pd.DataFrame, ticker: str) -> go.Figure:
|
263 |
+
"""Create RSI chart with overbought/oversold markers"""
|
264 |
+
fig = go.Figure()
|
265 |
+
|
266 |
+
fig.add_trace(go.Scatter(
|
267 |
+
x=data.index, y=data['RSI'],
|
268 |
+
name='RSI',
|
269 |
+
line=dict(color='#9b59b6', width=2)
|
270 |
+
))
|
271 |
+
|
272 |
+
# Add threshold lines
|
273 |
+
fig.add_hline(y=70, line_dash="dash", line_color="#e74c3c", annotation_text="Overbought")
|
274 |
+
fig.add_hline(y=30, line_dash="dash", line_color="#2ecc71", annotation_text="Oversold")
|
275 |
+
|
276 |
+
fig.update_layout(
|
277 |
+
title=f'{ticker} RSI (14-day)',
|
278 |
+
xaxis_title='Date',
|
279 |
+
yaxis_title='RSI Value',
|
280 |
+
hovermode='x unified',
|
281 |
+
template='plotly_dark',
|
282 |
+
height=300,
|
283 |
+
plot_bgcolor='#1e1e1e',
|
284 |
+
paper_bgcolor='#121212',
|
285 |
+
font=dict(color='white')
|
286 |
+
)
|
287 |
+
return fig
|
288 |
+
|
289 |
+
def create_macd_chart(data: pd.DataFrame, ticker: str) -> go.Figure:
|
290 |
+
"""Create MACD chart"""
|
291 |
+
fig = go.Figure()
|
292 |
+
|
293 |
+
if 'MACD' in data:
|
294 |
+
fig.add_trace(go.Scatter(
|
295 |
+
x=data.index, y=data['MACD'],
|
296 |
+
name='MACD',
|
297 |
+
line=dict(color='#3498db', width=2)
|
298 |
+
))
|
299 |
+
|
300 |
+
if 'MACD_Signal' in data:
|
301 |
+
fig.add_trace(go.Scatter(
|
302 |
+
x=data.index, y=data['MACD_Signal'],
|
303 |
+
name='Signal',
|
304 |
+
line=dict(color='#f39c12', width=2)
|
305 |
+
))
|
306 |
+
|
307 |
+
if 'MACD_Hist' in data:
|
308 |
+
colors = np.where(data['MACD_Hist'] < 0, '#e74c3c', '#2ecc71')
|
309 |
+
fig.add_trace(go.Bar(
|
310 |
+
x=data.index, y=data['MACD_Hist'],
|
311 |
+
name='Histogram',
|
312 |
+
marker_color=colors,
|
313 |
+
opacity=0.6
|
314 |
+
))
|
315 |
+
|
316 |
+
fig.update_layout(
|
317 |
+
title=f'{ticker} MACD',
|
318 |
+
xaxis_title='Date',
|
319 |
+
yaxis_title='Value',
|
320 |
+
hovermode='x unified',
|
321 |
+
template='plotly_dark',
|
322 |
+
height=300,
|
323 |
+
plot_bgcolor='#1e1e1e',
|
324 |
+
paper_bgcolor='#121212',
|
325 |
+
font=dict(color='white')
|
326 |
+
)
|
327 |
+
return fig
|
328 |
|
329 |
# === Main Application ===
|
330 |
def main():
|
|
|
332 |
st.markdown('<p class="header-title">Professional stock analysis with AI-powered insights</p>',
|
333 |
unsafe_allow_html=True)
|
334 |
|
335 |
+
# Sidebar controls
|
336 |
with st.sidebar:
|
337 |
st.header("βοΈ Settings")
|
338 |
default_ticker = st.text_input("Default Ticker", value="AAPL")
|
|
|
366 |
|
367 |
with st.spinner(f"π Analyzing {ticker}..."):
|
368 |
try:
|
369 |
+
# Get data with fallback
|
370 |
data, error_msg = get_live_data(ticker, selected_period)
|
371 |
is_dummy = error_msg is not None
|
372 |
|
|
|
376 |
else:
|
377 |
st.success("β
Fetched market data")
|
378 |
|
379 |
+
# Create tabs
|
380 |
tab1, tab2, tab3 = st.tabs(["π Charts", "π Data", "π§ Analysis"])
|
381 |
|
382 |
with tab1:
|
383 |
+
# Price chart
|
384 |
+
st.plotly_chart(create_price_chart(data, ticker, is_dummy),
|
385 |
+
use_container_width=True)
|
386 |
+
|
387 |
+
# Indicators in columns
|
388 |
+
if show_rsi or show_macd:
|
389 |
+
col1, col2 = st.columns(2)
|
390 |
+
|
391 |
+
with col1:
|
392 |
+
if show_rsi and 'RSI' in data:
|
393 |
+
st.plotly_chart(create_rsi_chart(data, ticker),
|
394 |
+
use_container_width=True)
|
395 |
+
|
396 |
+
with col2:
|
397 |
+
if show_macd and 'MACD' in data:
|
398 |
+
st.plotly_chart(create_macd_chart(data, ticker),
|
399 |
+
use_container_width=True)
|
400 |
+
|
401 |
+
# Key metrics
|
402 |
+
st.subheader("Key Metrics")
|
403 |
+
cols = st.columns(4)
|
404 |
+
|
405 |
+
metric_data = [
|
406 |
+
("Price", data['Close'].iloc[-1] if 'Close' in data else None, "${:.2f}"),
|
407 |
+
("RSI", data['RSI'].iloc[-1] if 'RSI' in data else None, "{:.1f}"),
|
408 |
+
("20-day MA", data['MA_20'].iloc[-1] if 'MA_20' in data else None, "${:.2f}"),
|
409 |
+
("50-day MA", data['MA_50'].iloc[-1] if 'MA_50' in data else None, "${:.2f}")
|
410 |
+
]
|
411 |
+
|
412 |
+
for i, (title, value, fmt) in enumerate(metric_data):
|
413 |
+
with cols[i]:
|
414 |
+
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
415 |
+
if value is not None:
|
416 |
+
delta = None
|
417 |
+
if title == "RSI":
|
418 |
+
status = ("Overbought" if value > 70
|
419 |
+
else "Oversold" if value < 30
|
420 |
+
else "Neutral")
|
421 |
+
st.metric(title, fmt.format(value), status)
|
422 |
+
elif "MA" in title:
|
423 |
+
price = data['Close'].iloc[-1]
|
424 |
+
ma_value = value
|
425 |
+
change = (price - ma_value)/ma_value*100
|
426 |
+
st.metric(title, fmt.format(ma_value), f"{change:.2f}%")
|
427 |
+
else:
|
428 |
+
st.metric(title, fmt.format(value))
|
429 |
+
else:
|
430 |
+
st.metric(title, "N/A")
|
431 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
432 |
|
433 |
with tab2:
|
434 |
+
st.subheader("Market Data")
|
435 |
+
if not data.empty:
|
436 |
+
display_data = data.tail(20).copy()
|
437 |
+
display_data.index = display_data.index.strftime('%Y-%m-%d')
|
438 |
+
|
439 |
+
cols_to_show = [col for col in ['Open', 'High', 'Low', 'Close', 'Volume',
|
440 |
+
'RSI', 'MA_20', 'MA_50'] if col in data]
|
441 |
+
|
442 |
+
st.dataframe(
|
443 |
+
display_data[cols_to_show].style.format("{:.2f}"),
|
444 |
+
use_container_width=True,
|
445 |
+
height=600
|
446 |
+
)
|
447 |
+
else:
|
448 |
+
st.warning("No data available")
|
449 |
|
450 |
with tab3:
|
451 |
st.subheader("AI Analysis")
|
|
|
462 |
|
463 |
if __name__ == "__main__":
|
464 |
if gemini_model: # Only run if Gemini initialized successfully
|
465 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|