Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import yfinance as yf | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import plotly.express as px | |
| from datetime import datetime, timedelta | |
| import numpy as np | |
| import anthropic | |
| import json | |
| # Page config | |
| st.set_page_config( | |
| page_title="Meta Stock Analysis", | |
| page_icon="π", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # Title and description | |
| st.title("π Meta (META) Stock Analysis Dashboard") | |
| st.markdown("Real-time analysis of Meta Platforms Inc. stock performance with AI-powered insights") | |
| # Sidebar for controls and chatbot | |
| st.sidebar.header("π API Configuration") | |
| # Anthropic API Key input | |
| api_key = st.sidebar.text_input( | |
| "Enter your Anthropic API Key:", | |
| type="password", | |
| help="Enter your Anthropic API key to enable the AI stock analyst chatbot" | |
| ) | |
| # Initialize session state for chat | |
| if 'chat_messages' not in st.session_state: | |
| st.session_state.chat_messages = [] | |
| # Sidebar for analysis parameters | |
| st.sidebar.header("π Analysis Parameters") | |
| # Time period selection | |
| period_options = { | |
| "1 Month": "1mo", | |
| "3 Months": "3mo", | |
| "6 Months": "6mo", | |
| "1 Year": "1y", | |
| "2 Years": "2y", | |
| "5 Years": "5y" | |
| } | |
| selected_period = st.sidebar.selectbox( | |
| "Select Time Period", | |
| options=list(period_options.keys()), | |
| index=3 # Default to 1 Year | |
| ) | |
| # Moving averages | |
| ma_short = st.sidebar.number_input("Short MA (days)", min_value=5, max_value=50, value=20) | |
| ma_long = st.sidebar.number_input("Long MA (days)", min_value=50, max_value=200, value=50) | |
| # Load data function | |
| # Cache for 5 minutes | |
| def load_stock_data(symbol, period): | |
| """Load stock data from Yahoo Finance""" | |
| try: | |
| ticker = yf.Ticker(symbol) | |
| data = ticker.history(period=period) | |
| info = ticker.info | |
| return data, info | |
| except Exception as e: | |
| st.error(f"Error loading data: {str(e)}") | |
| return None, None | |
| # Technical indicators functions | |
| def calculate_rsi(data, window=14): | |
| """Calculate RSI (Relative Strength Index)""" | |
| delta = data['Close'].diff() | |
| gain = (delta.where(delta > 0, 0)).rolling(window=window).mean() | |
| loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean() | |
| rs = gain / loss | |
| rsi = 100 - (100 / (1 + rs)) | |
| return rsi | |
| def calculate_macd(data, fast=12, slow=26, signal=9): | |
| """Calculate MACD indicator""" | |
| exp1 = data['Close'].ewm(span=fast).mean() | |
| exp2 = data['Close'].ewm(span=slow).mean() | |
| macd = exp1 - exp2 | |
| signal_line = macd.ewm(span=signal).mean() | |
| histogram = macd - signal_line | |
| return macd, signal_line, histogram | |
| def safe_format(value, format_str): | |
| """Safely format values that might be None or NaN""" | |
| if value is None or pd.isna(value): | |
| return "N/A" | |
| try: | |
| return format_str.format(value) | |
| except: | |
| return str(value) | |
| def get_stock_analysis_context(data, info): | |
| """Prepare stock data context for AI analysis""" | |
| if data is None or data.empty: | |
| return "No stock data available." | |
| # Current metrics | |
| current_price = data['Close'].iloc[-1] | |
| prev_close = data['Close'].iloc[-2] | |
| price_change = current_price - prev_close | |
| price_change_pct = (price_change / prev_close) * 100 | |
| # Technical indicators | |
| current_rsi = data['RSI'].iloc[-1] if 'RSI' in data.columns and not pd.isna(data['RSI'].iloc[-1]) else None | |
| current_macd = data['MACD'].iloc[-1] if 'MACD' in data.columns and not pd.isna(data['MACD'].iloc[-1]) else None | |
| current_macd_signal = data['MACD_Signal'].iloc[-1] if 'MACD_Signal' in data.columns and not pd.isna(data['MACD_Signal'].iloc[-1]) else None | |
| # Moving averages | |
| ma_short_val = data['MA_Short'].iloc[-1] if 'MA_Short' in data.columns and not pd.isna(data['MA_Short'].iloc[-1]) else None | |
| ma_long_val = data['MA_Long'].iloc[-1] if 'MA_Long' in data.columns and not pd.isna(data['MA_Long'].iloc[-1]) else None | |
| # Volume analysis | |
| current_volume = data['Volume'].iloc[-1] | |
| avg_volume = data['Volume'].rolling(20).mean().iloc[-1] | |
| # 52-week range | |
| week_52_high = data['High'].rolling(252).max().iloc[-1] if len(data) >= 252 else data['High'].max() | |
| week_52_low = data['Low'].rolling(252).min().iloc[-1] if len(data) >= 252 else data['Low'].min() | |
| # Calculate returns safely | |
| returns_5d = "N/A" | |
| returns_20d = "N/A" | |
| if len(data) >= 6: | |
| returns_5d = safe_format(((data['Close'].iloc[-1] / data['Close'].iloc[-6]) - 1) * 100, "{:+.2f}%") | |
| if len(data) >= 21: | |
| returns_20d = safe_format(((data['Close'].iloc[-1] / data['Close'].iloc[-21]) - 1) * 100, "{:+.2f}%") | |
| # RSI analysis | |
| rsi_status = "N/A" | |
| if current_rsi is not None: | |
| if current_rsi > 70: | |
| rsi_status = "(Overbought >70)" | |
| elif current_rsi < 30: | |
| rsi_status = "(Oversold <30)" | |
| else: | |
| rsi_status = "(Neutral)" | |
| # MACD analysis | |
| macd_position = "N/A" | |
| if current_macd is not None and current_macd_signal is not None: | |
| if current_macd > current_macd_signal: | |
| macd_position = "Bullish (MACD > Signal)" | |
| else: | |
| macd_position = "Bearish (MACD < Signal)" | |
| # Price vs MA analysis | |
| price_vs_short_ma = "N/A" | |
| price_vs_long_ma = "N/A" | |
| if ma_short_val is not None: | |
| if current_price > ma_short_val: | |
| pct_diff = ((current_price/ma_short_val)-1)*100 | |
| price_vs_short_ma = f"Above (+{pct_diff:.1f}%)" | |
| else: | |
| pct_diff = ((current_price/ma_short_val)-1)*100 | |
| price_vs_short_ma = f"Below ({pct_diff:.1f}%)" | |
| if ma_long_val is not None: | |
| if current_price > ma_long_val: | |
| pct_diff = ((current_price/ma_long_val)-1)*100 | |
| price_vs_long_ma = f"Above (+{pct_diff:.1f}%)" | |
| else: | |
| pct_diff = ((current_price/ma_long_val)-1)*100 | |
| price_vs_long_ma = f"Below ({pct_diff:.1f}%)" | |
| context = f"""META STOCK ANALYSIS DATA (Last Updated: {data.index[-1].strftime('%Y-%m-%d')}): | |
| CURRENT PRICE METRICS: | |
| - Current Price: ${current_price:.2f} | |
| - Daily Change: ${price_change:+.2f} ({price_change_pct:+.2f}%) | |
| - 52-Week High: ${week_52_high:.2f} | |
| - 52-Week Low: ${week_52_low:.2f} | |
| - Current Volume: {current_volume:,.0f} | |
| - 20-Day Avg Volume: {avg_volume:,.0f} | |
| TECHNICAL INDICATORS: | |
| - RSI (14): {safe_format(current_rsi, '{:.1f}')} {rsi_status} | |
| - MACD: {safe_format(current_macd, '{:.3f}')} | |
| - MACD Signal: {safe_format(current_macd_signal, '{:.3f}')} | |
| - MACD Position: {macd_position} | |
| MOVING AVERAGES: | |
| - {ma_short}-Day MA: ${safe_format(ma_short_val, '{:.2f}')} | |
| - {ma_long}-Day MA: ${safe_format(ma_long_val, '{:.2f}')} | |
| - Price vs Short MA: {price_vs_short_ma} | |
| - Price vs Long MA: {price_vs_long_ma} | |
| COMPANY INFO: | |
| - Company: {info.get('longName', 'Meta Platforms Inc.')} | |
| - Market Cap: ${safe_format(info.get('marketCap', 0)/1e9 if info.get('marketCap') else None, '{:.1f}B')} | |
| - P/E Ratio: {info.get('trailingPE', 'N/A')} | |
| - Sector: {info.get('sector', 'Technology')} | |
| RECENT PERFORMANCE: | |
| - 5-Day Return: {returns_5d} | |
| - 20-Day Return: {returns_20d} | |
| You are an expert stock analyst. Use this data to provide insights about whether META is overbought, oversold, or fairly valued based on technical indicators. | |
| """ | |
| return context | |
| def chat_with_ai(messages, api_key, stock_context): | |
| """Chat with Anthropic AI using stock context""" | |
| try: | |
| client = anthropic.Anthropic(api_key=api_key) | |
| # Prepare system message with stock context | |
| system_message = f"""You are an expert stock analyst AI assistant specializing in Meta (META) stock analysis. | |
| {stock_context} | |
| Instructions for analysis: | |
| 1. Use the technical indicators (RSI, MACD, Moving Averages) to determine if the stock is overbought, oversold, or neutral | |
| 2. Consider multiple timeframes and indicators together | |
| 3. Provide specific reasoning for your conclusions | |
| 4. Give actionable insights while noting this is not financial advice | |
| 5. Be concise but thorough in your analysis | |
| 6. If asked about other stocks, politely redirect to Meta analysis | |
| 7. Use the current data provided to give relevant, timely insights | |
| Key Analysis Rules: | |
| - RSI > 70: Potentially overbought | |
| - RSI < 30: Potentially oversold | |
| - MACD above signal line: Bullish momentum | |
| - MACD below signal line: Bearish momentum | |
| - Price above both MAs: Uptrend | |
| - Price below both MAs: Downtrend | |
| - Volume above average: Strong conviction in moves | |
| """ | |
| # Convert messages to Anthropic format | |
| formatted_messages = [] | |
| for msg in messages: | |
| formatted_messages.append({ | |
| "role": msg["role"], | |
| "content": msg["content"] | |
| }) | |
| response = client.messages.create( | |
| model="claude-3-haiku-20240307", | |
| max_tokens=1000, | |
| system=system_message, | |
| messages=formatted_messages | |
| ) | |
| return response.content[0].text | |
| except Exception as e: | |
| return f"Error: {str(e)}. Please check your API key and try again." | |
| # Load Meta stock data | |
| data, info = load_stock_data("META", period_options[selected_period]) | |
| if data is not None and not data.empty: | |
| # Calculate indicators | |
| data['MA_Short'] = data['Close'].rolling(window=ma_short).mean() | |
| data['MA_Long'] = data['Close'].rolling(window=ma_long).mean() | |
| data['RSI'] = calculate_rsi(data) | |
| data['MACD'], data['MACD_Signal'], data['MACD_Histogram'] = calculate_macd(data) | |
| # Get stock context for AI | |
| stock_context = get_stock_analysis_context(data, info) | |
| # Current price info | |
| current_price = data['Close'].iloc[-1] | |
| prev_close = data['Close'].iloc[-2] | |
| price_change = current_price - prev_close | |
| price_change_pct = (price_change / prev_close) * 100 | |
| # AI Chatbot in Sidebar | |
| st.sidebar.header("π€ AI Stock Analyst") | |
| if api_key: | |
| st.sidebar.success("β API Key configured - Chat enabled!") | |
| # Chat interface | |
| st.sidebar.subheader("π¬ Ask about META stock") | |
| # Display chat messages | |
| chat_container = st.sidebar.container() | |
| with chat_container: | |
| for message in st.session_state.chat_messages: | |
| if message["role"] == "user": | |
| st.write(f"**You:** {message['content']}") | |
| else: | |
| st.write(f"**AI:** {message['content']}") | |
| # Chat input | |
| user_question = st.sidebar.text_input( | |
| "Ask about META analysis:", | |
| placeholder="Is META overbought right now?", | |
| key="chat_input" | |
| ) | |
| if st.sidebar.button("Send", key="send_chat"): | |
| if user_question: | |
| # Add user message | |
| st.session_state.chat_messages.append({ | |
| "role": "user", | |
| "content": user_question | |
| }) | |
| # Get AI response | |
| with st.spinner("π€ AI analyzing..."): | |
| ai_response = chat_with_ai( | |
| st.session_state.chat_messages, | |
| api_key, | |
| stock_context | |
| ) | |
| # Add AI response | |
| st.session_state.chat_messages.append({ | |
| "role": "assistant", | |
| "content": ai_response | |
| }) | |
| st.rerun() | |
| # Quick analysis buttons | |
| st.sidebar.subheader("π― Quick Analysis") | |
| if st.sidebar.button("π Overall Analysis"): | |
| quick_question = "Based on all the technical indicators, is META currently overbought, oversold, or fairly valued? Give me a comprehensive analysis." | |
| st.session_state.chat_messages.append({"role": "user", "content": quick_question}) | |
| with st.spinner("π€ Analyzing..."): | |
| ai_response = chat_with_ai(st.session_state.chat_messages, api_key, stock_context) | |
| st.session_state.chat_messages.append({"role": "assistant", "content": ai_response}) | |
| st.rerun() | |
| if st.sidebar.button("π― Buy/Sell Signal"): | |
| quick_question = "Should I buy, sell, or hold META stock right now based on the technical indicators?" | |
| st.session_state.chat_messages.append({"role": "user", "content": quick_question}) | |
| with st.spinner("π€ Analyzing..."): | |
| ai_response = chat_with_ai(st.session_state.chat_messages, api_key, stock_context) | |
| st.session_state.chat_messages.append({"role": "assistant", "content": ai_response}) | |
| st.rerun() | |
| if st.sidebar.button("π Risk Assessment"): | |
| quick_question = "What are the current risks and opportunities for META stock based on the technical analysis?" | |
| st.session_state.chat_messages.append({"role": "user", "content": quick_question}) | |
| with st.spinner("π€ Analyzing..."): | |
| ai_response = chat_with_ai(st.session_state.chat_messages, api_key, stock_context) | |
| st.session_state.chat_messages.append({"role": "assistant", "content": ai_response}) | |
| st.rerun() | |
| # Clear chat button | |
| if st.sidebar.button("ποΈ Clear Chat"): | |
| st.session_state.chat_messages = [] | |
| st.rerun() | |
| else: | |
| st.sidebar.warning("β οΈ Enter Anthropic API key to enable AI chat") | |
| st.sidebar.info("Get your API key from: https://console.anthropic.com/") | |
| # Display key metrics | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| st.metric( | |
| "Current Price", | |
| f"${current_price:.2f}", | |
| f"{price_change:+.2f} ({price_change_pct:+.2f}%)" | |
| ) | |
| with col2: | |
| day_high = data['High'].iloc[-1] | |
| day_low = data['Low'].iloc[-1] | |
| st.metric("Day Range", f"${day_low:.2f} - ${day_high:.2f}") | |
| with col3: | |
| volume = data['Volume'].iloc[-1] | |
| avg_volume = data['Volume'].rolling(20).mean().iloc[-1] | |
| volume_ratio = volume / avg_volume | |
| st.metric( | |
| "Volume", | |
| f"{volume:,.0f}", | |
| f"{volume_ratio:.2f}x avg" | |
| ) | |
| with col4: | |
| market_cap = info.get('marketCap', 0) | |
| if market_cap: | |
| market_cap_b = market_cap / 1e9 | |
| st.metric("Market Cap", f"${market_cap_b:.1f}B") | |
| # Main price chart | |
| st.subheader("π Price Chart with Moving Averages") | |
| fig_price = go.Figure() | |
| # Candlestick chart | |
| fig_price.add_trace(go.Candlestick( | |
| x=data.index, | |
| open=data['Open'], | |
| high=data['High'], | |
| low=data['Low'], | |
| close=data['Close'], | |
| name="META" | |
| )) | |
| # Moving averages | |
| fig_price.add_trace(go.Scatter( | |
| x=data.index, | |
| y=data['MA_Short'], | |
| name=f"MA {ma_short}", | |
| line=dict(color='orange', width=2) | |
| )) | |
| fig_price.add_trace(go.Scatter( | |
| x=data.index, | |
| y=data['MA_Long'], | |
| name=f"MA {ma_long}", | |
| line=dict(color='red', width=2) | |
| )) | |
| fig_price.update_layout( | |
| title="Meta Stock Price with Moving Averages", | |
| yaxis_title="Price ($)", | |
| xaxis_title="Date", | |
| height=500, | |
| showlegend=True | |
| ) | |
| st.plotly_chart(fig_price, use_container_width=True) | |
| # Technical indicators | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.subheader("π RSI (Relative Strength Index)") | |
| fig_rsi = go.Figure() | |
| fig_rsi.add_trace(go.Scatter( | |
| x=data.index, | |
| y=data['RSI'], | |
| name="RSI", | |
| line=dict(color='purple', width=2) | |
| )) | |
| fig_rsi.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought (70)") | |
| fig_rsi.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold (30)") | |
| fig_rsi.update_layout( | |
| yaxis_title="RSI", | |
| xaxis_title="Date", | |
| height=300, | |
| yaxis=dict(range=[0, 100]) | |
| ) | |
| st.plotly_chart(fig_rsi, use_container_width=True) | |
| current_rsi = data['RSI'].iloc[-1] | |
| if pd.notna(current_rsi): | |
| if current_rsi > 70: | |
| st.warning(f"β οΈ RSI: {current_rsi:.1f} - Potentially Overbought") | |
| elif current_rsi < 30: | |
| st.success(f"β RSI: {current_rsi:.1f} - Potentially Oversold") | |
| else: | |
| st.info(f"βΉοΈ RSI: {current_rsi:.1f} - Neutral") | |
| with col2: | |
| st.subheader("π MACD") | |
| fig_macd = go.Figure() | |
| fig_macd.add_trace(go.Scatter( | |
| x=data.index, | |
| y=data['MACD'], | |
| name="MACD", | |
| line=dict(color='blue', width=2) | |
| )) | |
| fig_macd.add_trace(go.Scatter( | |
| x=data.index, | |
| y=data['MACD_Signal'], | |
| name="Signal", | |
| line=dict(color='red', width=2) | |
| )) | |
| fig_macd.add_trace(go.Bar( | |
| x=data.index, | |
| y=data['MACD_Histogram'], | |
| name="Histogram", | |
| opacity=0.7 | |
| )) | |
| fig_macd.update_layout( | |
| yaxis_title="MACD", | |
| xaxis_title="Date", | |
| height=300 | |
| ) | |
| st.plotly_chart(fig_macd, use_container_width=True) | |
| # Volume analysis | |
| st.subheader("π Volume Analysis") | |
| fig_volume = px.bar( | |
| x=data.index, | |
| y=data['Volume'], | |
| title="Daily Trading Volume" | |
| ) | |
| fig_volume.update_layout( | |
| yaxis_title="Volume", | |
| xaxis_title="Date", | |
| height=300 | |
| ) | |
| st.plotly_chart(fig_volume, use_container_width=True) | |
| # Company information | |
| if info: | |
| st.subheader("π’ Company Information") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.write(f"**Company:** {info.get('longName', 'Meta Platforms Inc.')}") | |
| st.write(f"**Sector:** {info.get('sector', 'N/A')}") | |
| st.write(f"**Industry:** {info.get('industry', 'N/A')}") | |
| st.write(f"**Employees:** {info.get('fullTimeEmployees', 'N/A'):,}" if info.get('fullTimeEmployees') else "**Employees:** N/A") | |
| with col2: | |
| st.write(f"**P/E Ratio:** {info.get('trailingPE', 'N/A')}") | |
| st.write(f"**52W High:** ${info.get('fiftyTwoWeekHigh', 'N/A')}") | |
| st.write(f"**52W Low:** ${info.get('fiftyTwoWeekLow', 'N/A')}") | |
| st.write(f"**Dividend Yield:** {info.get('dividendYield', 'N/A')}") | |
| # Company description | |
| if 'longBusinessSummary' in info: | |
| st.subheader("π Business Summary") | |
| st.write(info['longBusinessSummary']) | |
| # Performance summary | |
| st.subheader("π Performance Summary") | |
| # Calculate returns | |
| returns_1d = ((data['Close'].iloc[-1] / data['Close'].iloc[-2]) - 1) * 100 | |
| returns_1w = ((data['Close'].iloc[-1] / data['Close'].iloc[-8]) - 1) * 100 if len(data) >= 8 else None | |
| returns_1m = ((data['Close'].iloc[-1] / data['Close'].iloc[-22]) - 1) * 100 if len(data) >= 22 else None | |
| returns_ytd = ((data['Close'].iloc[-1] / data['Close'].iloc[0]) - 1) * 100 | |
| perf_col1, perf_col2, perf_col3, perf_col4 = st.columns(4) | |
| with perf_col1: | |
| st.metric("1 Day Return", f"{returns_1d:+.2f}%") | |
| with perf_col2: | |
| if returns_1w is not None: | |
| st.metric("1 Week Return", f"{returns_1w:+.2f}%") | |
| with perf_col3: | |
| if returns_1m is not None: | |
| st.metric("1 Month Return", f"{returns_1m:+.2f}%") | |
| with perf_col4: | |
| st.metric(f"{selected_period} Return", f"{returns_ytd:+.2f}%") | |
| else: | |
| st.error("β Unable to load Meta stock data. Please check your internet connection and try again.") | |
| # Footer | |
| st.markdown("---") | |
| st.markdown("**Disclaimer:** This is for educational purposes only and should not be considered as investment advice.") | |
| st.markdown("**AI Analysis:** The AI chatbot provides analysis based on technical indicators but should not be used as the sole basis for investment decisions.") | |
| st.markdown("Data source: Yahoo Finance via yfinance library β’ AI: Anthropic Claude") |