From c08673dacefdd6571b03bbc62ad1a7f4841d2143 Mon Sep 17 00:00:00 2001 From: Omar Marie Date: Mon, 21 Jul 2025 23:37:30 +0300 Subject: [PATCH] refactor: improve structure --- .../ai_stock_trading/components/__init__.py | 3 + .../components/chat_interface.py | 156 ++++++++ .../ai_stock_trading/core/__init__.py | 3 + .../ai_stock_trading/core/ai_assistant.py | 275 ++++++++++++++ .../ai_stock_trading/core/data_service.py | 119 ++++++ .../ai_stock_trading/main_app.py | 355 ++++-------------- 6 files changed, 625 insertions(+), 286 deletions(-) create mode 100644 week4/community-contributions/ai_stock_trading/components/__init__.py create mode 100644 week4/community-contributions/ai_stock_trading/components/chat_interface.py create mode 100644 week4/community-contributions/ai_stock_trading/core/__init__.py create mode 100644 week4/community-contributions/ai_stock_trading/core/ai_assistant.py create mode 100644 week4/community-contributions/ai_stock_trading/core/data_service.py diff --git a/week4/community-contributions/ai_stock_trading/components/__init__.py b/week4/community-contributions/ai_stock_trading/components/__init__.py new file mode 100644 index 0000000..34dac01 --- /dev/null +++ b/week4/community-contributions/ai_stock_trading/components/__init__.py @@ -0,0 +1,3 @@ +""" +UI Components for the Stock Trading Platform +""" diff --git a/week4/community-contributions/ai_stock_trading/components/chat_interface.py b/week4/community-contributions/ai_stock_trading/components/chat_interface.py new file mode 100644 index 0000000..da0c9e4 --- /dev/null +++ b/week4/community-contributions/ai_stock_trading/components/chat_interface.py @@ -0,0 +1,156 @@ +""" +Clean, professional chat interface component +""" + +import streamlit as st +from core.ai_assistant import ai_assistant +from core.data_service import data_service + + +class ChatInterface: + """Professional chat interface for stock analysis""" + + @staticmethod + def render(symbol: str, country: str): + """Render the chat interface""" + if not symbol: + st.warning("⚠️ Please select a stock from the sidebar.") + return + + # Display chat history + if 'chat_history' not in st.session_state: + st.session_state.chat_history = [] + + if st.session_state.chat_history: + for message in st.session_state.chat_history: + if message['role'] == 'user': + st.chat_message("user").write(message['content']) + else: + st.chat_message("assistant").write(message['content']) + else: + # Clean welcome message + basic_info = data_service.get_basic_info(symbol, country) + company_name = basic_info.get('company_name', symbol) + welcome_msg = f"👋 Hello! I'm your AI assistant for **{company_name} ({symbol})**. Ask me anything!" + st.chat_message("assistant").write(welcome_msg) + + # Chat input + user_input = st.chat_input("Ask about price, trends, analysis, trading recommendations...") + + if user_input: + # Add user message + st.session_state.chat_history.append({'role': 'user', 'content': user_input}) + + # Generate AI response (only loads data if tools are called) + with st.spinner("Thinking..."): + ai_response = ai_assistant.generate_response(user_input, symbol, country) + + # Add AI response + st.session_state.chat_history.append({'role': 'assistant', 'content': ai_response}) + st.rerun() + + # Quick actions (collapsed by default) + ChatInterface._render_quick_actions(symbol, country) + + @staticmethod + def _render_quick_actions(symbol: str, country: str): + """Render quick action buttons""" + with st.expander("🚀 Quick Actions", expanded=False): + col1, col2, col3, col4 = st.columns(4) + + with col1: + if st.button("📈 Price Info", use_container_width=True): + ChatInterface._add_price_info(symbol, country) + st.rerun() + + with col2: + if st.button("📊 30-Day Analysis", use_container_width=True): + ChatInterface._add_medium_term_analysis(symbol) + st.rerun() + + with col3: + if st.button("💰 Trading Rec", use_container_width=True): + ChatInterface._add_trading_recommendation(symbol, country) + st.rerun() + + with col4: + if st.button("☪️ Sharia", use_container_width=True): + ChatInterface._add_sharia_compliance(symbol, country) + st.rerun() + + @staticmethod + def _add_price_info(symbol: str, country: str): + """Add current price info to chat""" + basic_info = data_service.get_basic_info(symbol, country) + + current_price = basic_info.get('current_price', 0) + market_cap = basic_info.get('market_cap', 0) + sector = basic_info.get('sector', 'N/A') + + message = f"""📈 **Current Price Info for {symbol}:** + +💰 **Price:** ${current_price:.2f} +🏢 **Market Cap:** ${market_cap:,.0f} +🏭 **Sector:** {sector}""" + + st.session_state.chat_history.append({'role': 'assistant', 'content': message}) + + @staticmethod + def _add_medium_term_analysis(symbol: str): + """Add 30-day analysis to chat""" + analysis = data_service.get_analysis(symbol, "1mo") + + if 'error' in analysis: + message = f"❌ **30-Day Analysis:** {analysis['error']}" + else: + return_pct = analysis.get('total_return_pct', 0) + volatility = analysis.get('volatility_annualized', 0) + trend = analysis.get('trend_direction', 'neutral') + + message = f"""📊 **30-Day Analysis for {symbol}:** + +📈 **Return:** {return_pct:.2f}% +📉 **Volatility:** {volatility:.1f}% (annualized) +🎯 **Trend:** {trend.title()}""" + + st.session_state.chat_history.append({'role': 'assistant', 'content': message}) + + @staticmethod + def _add_trading_recommendation(symbol: str, country: str): + """Add trading recommendation to chat""" + trading = data_service.get_trading_recommendation(symbol, country) + + if 'error' in trading: + message = f"❌ **Trading Recommendation:** {trading['error']}" + else: + rec = trading.get('recommendation', 'HOLD') + conf = trading.get('confidence', 0.5) * 100 + reasoning = trading.get('reasoning', 'No reasoning available') + + message = f"""💰 **Trading Recommendation for {symbol}:** + +🎯 **Action:** {rec} +📊 **Confidence:** {conf:.0f}% +💭 **Reasoning:** {reasoning[:200]}...""" + + st.session_state.chat_history.append({'role': 'assistant', 'content': message}) + + @staticmethod + def _add_sharia_compliance(symbol: str, country: str): + """Add Sharia compliance to chat""" + sharia = data_service.get_sharia_compliance(symbol, country) + + if 'error' in sharia: + message = f"❌ **Sharia Compliance:** {sharia['error']}" + else: + ruling = sharia.get('ruling', 'UNCERTAIN') + conf = sharia.get('confidence', 0.5) * 100 + + status_emoji = "✅" if ruling == "HALAL" else "❌" if ruling == "HARAM" else "⚠️" + + message = f"""☪️ **Sharia Compliance for {symbol}:** + +{status_emoji} **Ruling:** {ruling} +📊 **Confidence:** {conf:.0f}%""" + + st.session_state.chat_history.append({'role': 'assistant', 'content': message}) diff --git a/week4/community-contributions/ai_stock_trading/core/__init__.py b/week4/community-contributions/ai_stock_trading/core/__init__.py new file mode 100644 index 0000000..f3d485c --- /dev/null +++ b/week4/community-contributions/ai_stock_trading/core/__init__.py @@ -0,0 +1,3 @@ +""" +Core module for AI Stock Trading Platform +""" diff --git a/week4/community-contributions/ai_stock_trading/core/ai_assistant.py b/week4/community-contributions/ai_stock_trading/core/ai_assistant.py new file mode 100644 index 0000000..0255491 --- /dev/null +++ b/week4/community-contributions/ai_stock_trading/core/ai_assistant.py @@ -0,0 +1,275 @@ +import os +from typing import Dict, Any, List +from openai import OpenAI +from .data_service import data_service + + +class AIAssistant: + """Enhanced AI assistant with comprehensive stock analysis tools""" + + def __init__(self): + self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY')) + + def get_enhanced_tools(self) -> List[Dict[str, Any]]: + """Get comprehensive tool definitions for OpenAI function calling""" + return [ + { + "type": "function", + "function": { + "name": "get_current_price_info", + "description": "Get current price, basic metrics, and company info", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_short_term_analysis", + "description": "Get 10-day technical analysis and short-term trends", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_medium_term_analysis", + "description": "Get 30-day technical analysis and medium-term trends", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_long_term_analysis", + "description": "Get 90-day technical analysis and long-term trends", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_comprehensive_analysis", + "description": "Get full 1-year technical analysis with all indicators", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_trading_recommendation", + "description": "Get buy/hold/sell recommendation with price targets and reasoning", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_sharia_compliance", + "description": "Get Islamic finance compliance analysis", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + }, + { + "type": "function", + "function": { + "name": "compare_time_periods", + "description": "Compare performance across multiple time periods (10d, 30d, 90d)", + "parameters": { + "type": "object", + "properties": { + "symbol": {"type": "string", "description": "Stock symbol"} + }, + "required": ["symbol"] + } + } + } + ] + + def generate_response(self, user_input: str, symbol: str, country: str) -> str: + """Generate AI response with enhanced tool calling""" + try: + # Get basic info without heavy loading + basic_info = data_service.get_basic_info(symbol, country) + + system_msg = f"""You are a professional financial advisor assistant for {symbol}. + +IMPORTANT: Only call tools when users specifically request: +- Price information or basic metrics → get_current_price_info +- Short-term analysis (10 days) → get_short_term_analysis +- Medium-term analysis (30 days) → get_medium_term_analysis +- Long-term analysis (90 days) → get_long_term_analysis +- Comprehensive analysis (1 year) → get_comprehensive_analysis +- Trading recommendations → get_trading_recommendation +- Sharia compliance → get_sharia_compliance +- Time period comparisons → compare_time_periods + +For general questions about the company, market commentary, or basic information, respond directly without calling tools. +Keep responses concise and professional.""" + + user_msg = f"""Stock: {symbol} ({basic_info.get('company_name', 'N/A')}) +Country: {country} +Sector: {basic_info.get('sector', 'N/A')} +User Question: {user_input}""" + + response = self.client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": system_msg}, + {"role": "user", "content": user_msg} + ], + tools=self.get_enhanced_tools(), # type: ignore + tool_choice="auto", + temperature=0.7, + max_tokens=600 + ) + + message = response.choices[0].message + + if message.tool_calls: + return self._handle_tool_calls(message.tool_calls, user_input, symbol, country) + + return message.content or "I apologize, but I couldn't generate a response." + + except Exception as e: + return f"Sorry, I encountered an error: {str(e)}" + + def _handle_tool_calls(self, tool_calls, user_input: str, symbol: str, country: str) -> str: + """Handle tool calls and generate final response""" + tool_results = [] + + for tool_call in tool_calls: + function_name = tool_call.function.name + + try: + if function_name == "get_current_price_info": + basic_info = data_service.get_basic_info(symbol, country) + current_price = basic_info.get('current_price', 0) + market_cap = basic_info.get('market_cap', 0) + tool_results.append(f"Current Price: ${current_price:.2f}, Market Cap: ${market_cap:,.0f}") + + elif function_name == "get_short_term_analysis": + analysis = data_service.get_analysis(symbol, "10d") + if 'error' not in analysis: + return_pct = analysis.get('total_return_pct', 0) + volatility = analysis.get('volatility_annualized', 0) + tool_results.append(f"10-Day Analysis: Return {return_pct:.2f}%, Volatility {volatility:.1f}%") + else: + tool_results.append("10-Day Analysis: Data unavailable") + + elif function_name == "get_medium_term_analysis": + analysis = data_service.get_analysis(symbol, "1mo") + if 'error' not in analysis: + return_pct = analysis.get('total_return_pct', 0) + trend = analysis.get('trend_direction', 'neutral') + tool_results.append(f"30-Day Analysis: Return {return_pct:.2f}%, Trend {trend}") + else: + tool_results.append("30-Day Analysis: Data unavailable") + + elif function_name == "get_long_term_analysis": + analysis = data_service.get_analysis(symbol, "3mo") + if 'error' not in analysis: + return_pct = analysis.get('total_return_pct', 0) + sharpe = analysis.get('sharpe_ratio', 0) + tool_results.append(f"90-Day Analysis: Return {return_pct:.2f}%, Sharpe {sharpe:.2f}") + else: + tool_results.append("90-Day Analysis: Data unavailable") + + elif function_name == "get_comprehensive_analysis": + analysis = data_service.get_analysis(symbol, "1y") + if 'error' not in analysis: + return_pct = analysis.get('total_return_pct', 0) + max_drawdown = analysis.get('max_drawdown', 0) + rsi = analysis.get('rsi', 50) + tool_results.append(f"1-Year Analysis: Return {return_pct:.2f}%, Max Drawdown {max_drawdown:.1f}%, RSI {rsi:.1f}") + else: + tool_results.append("1-Year Analysis: Data unavailable") + + elif function_name == "get_trading_recommendation": + trading = data_service.get_trading_recommendation(symbol, country) + if 'error' not in trading: + rec = trading.get('recommendation', 'HOLD') + conf = trading.get('confidence', 0.5) * 100 + tool_results.append(f"Trading: {rec} ({conf:.0f}% confidence)") + else: + tool_results.append("Trading: Analysis unavailable") + + elif function_name == "get_sharia_compliance": + sharia = data_service.get_sharia_compliance(symbol, country) + if 'error' not in sharia: + ruling = sharia.get('ruling', 'UNCERTAIN') + conf = sharia.get('confidence', 0.5) * 100 + tool_results.append(f"Sharia: {ruling} ({conf:.0f}% confidence)") + else: + tool_results.append("Sharia: Analysis unavailable") + + elif function_name == "compare_time_periods": + periods = ["10d", "1mo", "3mo"] + comparisons = [] + for period in periods: + analysis = data_service.get_analysis(symbol, period) + if 'error' not in analysis: + return_pct = analysis.get('total_return_pct', 0) + comparisons.append(f"{period}: {return_pct:.2f}%") + tool_results.append(f"Period Comparison: {', '.join(comparisons)}") + + except Exception as e: + tool_results.append(f"{function_name}: Error - {str(e)}") + + # Generate final response + final_response = self.client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": "Provide a concise, professional response based on the tool results. Focus on actionable insights."}, + {"role": "user", "content": f"Question: {user_input}\n\nTool Results: {' | '.join(tool_results)}"} + ], + temperature=0.7, + max_tokens=500 + ) + + return final_response.choices[0].message.content or "I couldn't generate a response." + + +# Global instance +ai_assistant = AIAssistant() diff --git a/week4/community-contributions/ai_stock_trading/core/data_service.py b/week4/community-contributions/ai_stock_trading/core/data_service.py new file mode 100644 index 0000000..a5232bb --- /dev/null +++ b/week4/community-contributions/ai_stock_trading/core/data_service.py @@ -0,0 +1,119 @@ +import streamlit as st +from typing import Dict, Any, Optional +from tools.fetching import stock_fetcher +from tools.analysis import stock_analyzer +from tools.trading_decisions import trading_engine +from tools.sharia_compliance import sharia_checker + + +class DataService: + """Centralized data service for efficient stock data management""" + + @staticmethod + def get_basic_info(symbol: str, country: str) -> Dict[str, Any]: + """Get only basic stock info - no heavy analysis""" + cache_key = f"{symbol}_basic" + + if cache_key not in st.session_state: + try: + stock_info = stock_fetcher.get_stock_info(symbol, country) + st.session_state[cache_key] = stock_info + except Exception as e: + st.session_state[cache_key] = { + 'company_name': symbol, + 'error': str(e) + } + + return st.session_state[cache_key] + + @staticmethod + def get_price_data(symbol: str, period: str = "1y") -> Dict[str, Any]: + """Get price data for specific period""" + cache_key = f"{symbol}_data_{period}" + + if cache_key not in st.session_state: + try: + data = stock_fetcher.fetch_stock_data(symbol, period=period) + st.session_state[cache_key] = data + except Exception as e: + st.session_state[cache_key] = None + st.error(f"Failed to load {period} data: {str(e)}") + + return st.session_state[cache_key] + + @staticmethod + def get_analysis(symbol: str, period: str = "1y") -> Dict[str, Any]: + """Get technical analysis for specific period""" + cache_key = f"{symbol}_analysis_{period}" + + if cache_key not in st.session_state: + data = DataService.get_price_data(symbol, period) + if data is not None and hasattr(data, 'empty') and not data.empty: + try: + analysis = stock_analyzer.analyze_stock(data) + analysis['period'] = period + st.session_state[cache_key] = analysis + except Exception as e: + st.session_state[cache_key] = {'error': f"Analysis failed: {str(e)}"} + else: + st.session_state[cache_key] = {'error': 'No data available'} + + return st.session_state[cache_key] + + @staticmethod + def get_trading_recommendation(symbol: str, country: str) -> Dict[str, Any]: + """Get trading recommendation""" + cache_key = f"{symbol}_trading" + + if cache_key not in st.session_state: + try: + analysis = DataService.get_analysis(symbol) + stock_info = DataService.get_basic_info(symbol, country) + + if 'error' not in analysis and 'error' not in stock_info: + trading = trading_engine.get_trading_recommendation(symbol, analysis, stock_info) + st.session_state[cache_key] = trading + else: + st.session_state[cache_key] = {'error': 'Cannot generate recommendation'} + except Exception as e: + st.session_state[cache_key] = {'error': f"Trading analysis failed: {str(e)}"} + + return st.session_state[cache_key] + + @staticmethod + def get_sharia_compliance(symbol: str, country: str) -> Dict[str, Any]: + """Get Sharia compliance analysis""" + cache_key = f"{symbol}_sharia" + + if cache_key not in st.session_state: + try: + stock_info = DataService.get_basic_info(symbol, country) + analysis = DataService.get_analysis(symbol) + + if 'error' not in stock_info: + sharia = sharia_checker.check_sharia_compliance(symbol, stock_info, analysis) + st.session_state[cache_key] = sharia + else: + st.session_state[cache_key] = {'error': 'Cannot check compliance'} + except Exception as e: + st.session_state[cache_key] = {'error': f"Sharia check failed: {str(e)}"} + + return st.session_state[cache_key] + + @staticmethod + def clear_cache(symbol: Optional[str] = None): + """Clear cached data""" + if symbol: + keys_to_remove = [key for key in st.session_state.keys() if isinstance(key, str) and key.startswith(f"{symbol}_")] + for key in keys_to_remove: + del st.session_state[key] + else: + # Clear all cache + keys_to_remove = [key for key in st.session_state.keys() + if isinstance(key, str) and ('_data_' in key or '_analysis_' in key or '_trading' in key or '_sharia' in key or '_basic' in key)] + for key in keys_to_remove: + del st.session_state[key] + + +# Global instance +data_service = DataService() diff --git a/week4/community-contributions/ai_stock_trading/main_app.py b/week4/community-contributions/ai_stock_trading/main_app.py index b2e5a04..a55622f 100644 --- a/week4/community-contributions/ai_stock_trading/main_app.py +++ b/week4/community-contributions/ai_stock_trading/main_app.py @@ -14,6 +14,11 @@ from tools.trading_decisions import trading_engine from tools.sharia_compliance import sharia_checker from tools.charting import chart_generator +# Import new modular components +from core.data_service import data_service +from core.ai_assistant import ai_assistant +from components.chat_interface import ChatInterface + # Load environment variables load_dotenv() @@ -211,264 +216,20 @@ class StockTradingApp: st.error(f"Error loading quick analysis: {str(e)}") def load_stock_analysis(self, symbol: str): - try: - country = st.session_state.selected_country - data = stock_fetcher.fetch_stock_data(symbol, period="1y") - stock_info = stock_fetcher.get_stock_info(symbol, country) - analysis = stock_analyzer.analyze_stock(data) - trading_decision = trading_engine.get_trading_recommendation(symbol, analysis, stock_info) - sharia_compliance = sharia_checker.check_sharia_compliance(symbol, stock_info, analysis) - - st.session_state.stock_data[symbol] = { - 'data': data, - 'stock_info': stock_info, - 'analysis': analysis, - 'trading_decision': trading_decision, - 'sharia_compliance': sharia_compliance - } - except Exception as e: - st.error(f"Error loading analysis for {symbol}: {str(e)}") + """Load complete analysis using data service""" + country = st.session_state.selected_country + # Pre-load all analysis components + data_service.get_analysis(symbol) + data_service.get_trading_recommendation(symbol, country) + data_service.get_sharia_compliance(symbol, country) def render_chat_page(self): st.header("💬 AI Stock Analysis Chat") - if not st.session_state.selected_stock: - st.warning("⚠️ Please select a stock from the sidebar to start chatting.") - return - symbol = st.session_state.selected_stock - st.info(f"💬 Chatting about: **{symbol}**") - - if symbol not in st.session_state.stock_data: - with st.spinner("Loading stock data and analysis..."): - self.load_stock_analysis(symbol) - - self.render_chat_interface() - - def render_chat_interface(self): - symbol = st.session_state.selected_stock - - if st.session_state.chat_history: - for message in st.session_state.chat_history: - if message['role'] == 'user': - st.chat_message("user").write(message['content']) - else: - st.chat_message("assistant").write(message['content']) - else: - welcome_msg = f""" - 👋 Hello! I'm your AI stock analysis assistant. I can help you with: - - • **Technical Analysis** of {symbol} - • **Trading Recommendations** (Buy/Hold/Sell) - • **Sharia Compliance** assessment - • **Risk Analysis** and market insights - - What would you like to know about {symbol}? - """ - st.chat_message("assistant").write(welcome_msg) - - user_input = st.chat_input("Ask me anything about this stock...") - - if user_input: - st.session_state.chat_history.append({'role': 'user', 'content': user_input}) - - with st.spinner("Analyzing..."): - ai_response = self.generate_ai_response(user_input, symbol) - - st.session_state.chat_history.append({'role': 'assistant', 'content': ai_response}) - st.rerun() - - st.subheader("🚀 Quick Actions") - col1, col2, col3, col4 = st.columns(4) - - with col1: - if st.button("📊 Get Analysis"): - self.add_analysis_to_chat(symbol) - st.rerun() - - with col2: - if st.button("💰 Trading Rec"): - self.add_trading_to_chat(symbol) - st.rerun() - - with col3: - if st.button("☪️ Sharia Check"): - self.add_sharia_to_chat(symbol) - st.rerun() - - with col4: - if st.button("🎯 Price Target"): - self.add_target_to_chat(symbol) - st.rerun() - - def generate_ai_response(self, user_input: str, symbol: str) -> str: - try: - from openai import OpenAI - client = OpenAI(api_key=os.getenv('OPENAI_API_KEY')) - - # Check if user is asking about Sharia compliance - sharia_keywords = ['sharia', 'halal', 'haram', 'islamic', 'muslim', 'compliant', 'permissible', 'forbidden'] - is_sharia_query = any(keyword in user_input.lower() for keyword in sharia_keywords) - - stock_data = st.session_state.stock_data.get(symbol, {}) - analysis = stock_data.get('analysis', {}) - trading_decision = stock_data.get('trading_decision', {}) - stock_info = stock_data.get('stock_info', {}) - country = st.session_state.selected_country - - # Format price with proper currency - current_price = analysis.get('current_price', 0) - formatted_price = stock_fetcher.format_price_with_currency(current_price, country) - - # Base context without Sharia info - context = f""" - You are analyzing {symbol} ({stock_info.get('company_name', 'N/A')}). - - Current Price: {formatted_price} - Return: {analysis.get('total_return_pct', 0):.2f}% - Recommendation: {trading_decision.get('recommendation', 'N/A')} - Sector: {stock_info.get('sector', 'N/A')} - - User Question: {user_input} - - Provide helpful analysis based on the available data. - """ - - # Add Sharia context only if user asks about it - if is_sharia_query: - # Load Sharia compliance if not already loaded - if symbol not in st.session_state.stock_data or 'sharia_compliance' not in st.session_state.stock_data[symbol]: - with st.spinner("Loading Sharia compliance analysis..."): - self.load_stock_analysis(symbol) - - sharia_compliance = st.session_state.stock_data.get(symbol, {}).get('sharia_compliance', {}) - context += f""" - - SHARIA COMPLIANCE ANALYSIS: - Ruling: {sharia_compliance.get('ruling', 'N/A')} - Confidence: {sharia_compliance.get('confidence', 0)*100:.0f}% - Reasoning: {sharia_compliance.get('reasoning', 'N/A')} - - Focus your response on Islamic finance principles and Sharia compliance. - """ - - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": "You are a financial advisor and Islamic finance expert."}, - {"role": "user", "content": context} - ], - temperature=0.7, - max_tokens=400 - ) - - return response.choices[0].message.content - - except Exception as e: - return f"Sorry, I'm having trouble right now. Error: {str(e)}" - - def add_analysis_to_chat(self, symbol: str): - stock_data = st.session_state.stock_data.get(symbol, {}) - analysis = stock_data.get('analysis', {}) - - if analysis: - summary = stock_analyzer.get_analysis_summary(analysis) - st.session_state.chat_history.append({ - 'role': 'assistant', - 'content': f"📊 **Analysis Summary for {symbol}:**\n\n{summary}" - }) - - def add_trading_to_chat(self, symbol: str): - stock_data = st.session_state.stock_data.get(symbol, {}) - trading_decision = stock_data.get('trading_decision', {}) - stock_info = stock_data.get('stock_info', {}) country = st.session_state.selected_country - if trading_decision: - rec = trading_decision.get('recommendation', 'HOLD') - conf = trading_decision.get('confidence', 0) - - # Handle confidence as percentage if it's already 0-100, or as decimal if 0-1 - if conf <= 1.0: - conf_pct = conf * 100 - else: - conf_pct = conf - - reason = trading_decision.get('reasoning', 'No reasoning available') - price_target = trading_decision.get('price_target') - stop_loss = trading_decision.get('stop_loss') - time_horizon = trading_decision.get('time_horizon', 'medium') - risk_level = trading_decision.get('risk_level', 'medium') - - # Clean reasoning - remove JSON artifacts - if reason.startswith('```json') or reason.startswith('{'): - # Extract readable content from malformed JSON - if 'reasoning' in reason: - try: - import re - reasoning_match = re.search(r'"reasoning"\s*:\s*"([^"]+)"', reason) - if reasoning_match: - reason = reasoning_match.group(1) - else: - reason = "Technical analysis suggests this recommendation based on current market conditions." - except: - reason = "Technical analysis suggests this recommendation based on current market conditions." - - # Format the message professionally - message_parts = [ - f"💰 **Trading Recommendation: {rec}**", - f"📊 **Confidence Level:** {conf_pct:.0f}%", - f"⏱️ **Time Horizon:** {time_horizon.title()}-term", - f"⚠️ **Risk Level:** {risk_level.title()}", - "", - f"**Analysis:**", - reason - ] - - # Add price targets if available - if price_target: - formatted_target = stock_fetcher.format_price_with_currency(price_target, country) - message_parts.append(f"🎯 **Price Target:** {formatted_target}") - - if stop_loss: - formatted_stop = stock_fetcher.format_price_with_currency(stop_loss, country) - message_parts.append(f"🛡️ **Stop Loss:** {formatted_stop}") - - message_parts.append("") - message_parts.append("*This is not financial advice. Please do your own research and consult with a financial advisor.*") - - message = "\n".join(message_parts) - st.session_state.chat_history.append({'role': 'assistant', 'content': message}) - - def add_sharia_to_chat(self, symbol: str): - stock_data = st.session_state.stock_data.get(symbol, {}) - sharia_compliance = stock_data.get('sharia_compliance', {}) - - if sharia_compliance: - summary = sharia_checker.get_compliance_summary(sharia_compliance) - st.session_state.chat_history.append({ - 'role': 'assistant', - 'content': f"☪️ **Sharia Compliance:**\n\n{summary}" - }) - - def add_target_to_chat(self, symbol: str): - stock_data = st.session_state.stock_data.get(symbol, {}) - trading_decision = stock_data.get('trading_decision', {}) - analysis = stock_data.get('analysis', {}) - - current = analysis.get('current_price', 0) - target = trading_decision.get('price_target') - stop = trading_decision.get('stop_loss') - - message = f"🎯 **Current Price:** ${current:.2f}\n" - if target: - upside = ((target - current) / current) * 100 - message += f"**Target:** ${target:.2f} ({upside:+.1f}%)\n" - if stop: - downside = ((stop - current) / current) * 100 - message += f"**Stop Loss:** ${stop:.2f} ({downside:+.1f}%)" - - st.session_state.chat_history.append({'role': 'assistant', 'content': message}) + ChatInterface.render(symbol, country) def render_dashboard_page(self): st.header("📊 Dashboard") @@ -480,27 +241,29 @@ class StockTradingApp: symbol = st.session_state.selected_stock country = st.session_state.selected_country - if symbol not in st.session_state.stock_data: - with st.spinner("Loading analysis..."): - self.load_stock_analysis(symbol) + # Load data using new data service + with st.spinner("Loading dashboard data..."): + basic_info = data_service.get_basic_info(symbol, country) + data = data_service.get_price_data(symbol, "1y") + analysis = data_service.get_analysis(symbol, "1y") + trading_decision = data_service.get_trading_recommendation(symbol, country) + sharia_compliance = data_service.get_sharia_compliance(symbol, country) - stock_data = st.session_state.stock_data.get(symbol, {}) - if not stock_data: - st.error("Failed to load data.") + # Check if data loaded successfully + if data is None or analysis.get('error') or trading_decision.get('error'): + st.error("Failed to load dashboard data. Please try again.") return - analysis = stock_data.get('analysis', {}) - trading_decision = stock_data.get('trading_decision', {}) - sharia_compliance = stock_data.get('sharia_compliance', {}) - data = stock_data.get('data') - # KPIs at the top col1, col2, col3, col4, col5 = st.columns(5) with col1: - current_price = data['Close'].iloc[-1] - formatted_price = stock_fetcher.format_price_with_currency(current_price, country) - st.metric("💰 Current Price", formatted_price) + if data is not None and hasattr(data, 'iloc') and len(data) > 0: + current_price = data['Close'].iloc[-1] + formatted_price = stock_fetcher.format_price_with_currency(current_price, country) + st.metric("💰 Current Price", formatted_price) + else: + st.metric("💰 Current Price", "N/A") with col2: total_return = analysis.get('total_return_pct', 0) @@ -508,36 +271,56 @@ class StockTradingApp: with col3: rec = trading_decision.get('recommendation', 'HOLD') - conf = trading_decision.get('confidence', 0) * 100 - st.metric("Recommendation", rec, f"{conf:.0f}% confidence") + conf = trading_decision.get('confidence', 0.5) + if conf <= 1.0: + conf_pct = conf * 100 + else: + conf_pct = conf + st.metric("Recommendation", rec, f"{conf_pct:.0f}% confidence") with col4: ruling = sharia_compliance.get('ruling', 'UNCERTAIN') - sharia_conf = sharia_compliance.get('confidence', 0) * 100 - st.metric("Sharia Status", ruling, f"{sharia_conf:.0f}% confidence") + sharia_conf = sharia_compliance.get('confidence', 0.5) + if sharia_conf <= 1.0: + sharia_conf_pct = sharia_conf * 100 + else: + sharia_conf_pct = sharia_conf + st.metric("Sharia Status", ruling, f"{sharia_conf_pct:.0f}% confidence") with col5: volatility = analysis.get('volatility_annualized', 0) st.metric("Volatility", f"{volatility:.1f}%") - st.divider() - # Charts section - - # First row: Risk Analysis and Trading Signals - col1, col2 = st.columns(2) - - with col1: - risk_fig = chart_generator.create_risk_analysis_chart(analysis, symbol) - st.plotly_chart(risk_fig, use_container_width=True) - - with col2: - signals_fig = chart_generator.create_trading_signals_chart(data, analysis, trading_decision, symbol) - st.plotly_chart(signals_fig, use_container_width=True) - - # Second row: Price Chart (full width) - price_fig = chart_generator.create_price_chart(data, symbol, analysis) - st.plotly_chart(price_fig, use_container_width=True) + # Charts section (only if data is available) + if data is not None and hasattr(data, 'iloc') and len(data) > 0: + st.divider() + + # First row: Risk Analysis and Trading Signals + col1, col2 = st.columns(2) + + with col1: + try: + risk_fig = chart_generator.create_risk_analysis_chart(analysis, symbol) + st.plotly_chart(risk_fig, use_container_width=True) + except Exception as e: + st.error(f"Risk chart error: {str(e)}") + + with col2: + try: + signals_fig = chart_generator.create_trading_signals_chart(data, analysis, trading_decision, symbol) + st.plotly_chart(signals_fig, use_container_width=True) + except Exception as e: + st.error(f"Signals chart error: {str(e)}") + + # Second row: Price Chart (full width) + try: + price_fig = chart_generator.create_price_chart(data, symbol, analysis) + st.plotly_chart(price_fig, use_container_width=True) + except Exception as e: + st.error(f"Price chart error: {str(e)}") + else: + st.warning("📊 Charts unavailable - no price data loaded.")