""" Stock Oracle - Investment Data Analyzer Python client integration with Stock Oracle API for comprehensive financial analysis. """ import pandas as pd import numpy as np from datetime import datetime, timedelta from typing import Dict, List, Optional, Union import logging import warnings from stock_oracle_client import StockOracleClient, StockOracleAPIError warnings.filterwarnings('ignore') class StockOracleAnalyzer: """ Stock Oracle - Comprehensive financial data analyzer using Stock Oracle API """ def __init__(self, api_url: str = "http://localhost:18001"): """ Initialize the analyzer with Stock Oracle API Args: api_url: Stock Oracle API URL """ self.client = StockOracleClient(api_url) self.logger = self._setup_logging() # Test connection try: health = self.client.get_health() self.logger.info(f"Connected to Stock Oracle API: {health.get('status')}") except Exception as e: self.logger.error(f"Failed to connect to Stock Oracle API: {e}") def _setup_logging(self) -> logging.Logger: """Set up logging configuration""" logging.basicConfig(level=logging.INFO) return logging.getLogger(__name__) def get_company_data(self, ticker: str, period: str = "5y") -> Dict: """ Retrieve comprehensive company data from Stock Oracle API Args: ticker: Stock ticker symbol period: Time period for data (e.g., "1y", "3y", "5y") Returns: Dict containing financial data, price data, and analysis """ try: self.logger.info(f"Retrieving data for {ticker}") # Get financial data financial_data = self.client.get_financial_data( ticker=ticker, period=period, include_metrics=True ) # Get price data price_data = self.client.get_price_data( ticker=ticker, period=period ) # Get news and social media data try: news_social_data = self.client.get_news_social_data( ticker=ticker, days_back=30, max_articles=50 ) except Exception as e: self.logger.warning(f"Could not retrieve news/social data: {e}") news_social_data = None # Combine all data data = { 'ticker': ticker, 'company_name': financial_data.get('company', {}).get('name'), 'financial_data': financial_data, 'price_data': price_data, 'news_social_data': news_social_data, 'analysis_summary': self._generate_analysis_summary(financial_data, price_data, news_social_data) } return data except StockOracleAPIError as e: self.logger.error(f"API Error retrieving data for {ticker}: {str(e)}") return None except Exception as e: self.logger.error(f"Error retrieving data for {ticker}: {str(e)}") return None def _generate_analysis_summary(self, financial_data: Dict, price_data: Dict, news_social_data: Optional[Dict]) -> Dict: """Generate analysis summary from all available data""" summary = { 'financial_health': self._assess_financial_health(financial_data), 'price_trends': self._analyze_price_trends(price_data), 'sentiment_analysis': self._analyze_sentiment(news_social_data) if news_social_data else None, 'key_metrics': self._extract_key_metrics(financial_data), 'investment_score': None # Will be calculated based on all factors } # Calculate overall investment score (0-100) summary['investment_score'] = self._calculate_investment_score(summary) return summary def _assess_financial_health(self, financial_data: Dict) -> Dict: """Assess financial health based on financial metrics""" health_score = 0 max_score = 100 assessment = {} try: latest_data = financial_data.get('financial_data', []) if not latest_data: return {'score': 0, 'assessment': 'No financial data available'} latest = latest_data[0] # Most recent data # Revenue growth assessment if len(latest_data) >= 2: current_revenue = latest.get('revenue', 0) previous_revenue = latest_data[1].get('revenue', 0) if previous_revenue > 0: revenue_growth = (current_revenue - previous_revenue) / previous_revenue assessment['revenue_growth'] = revenue_growth if revenue_growth > 0.15: # >15% growth health_score += 25 elif revenue_growth > 0.05: # >5% growth health_score += 15 elif revenue_growth > 0: # Positive growth health_score += 10 # Profitability assessment if 'pe_ratio' in latest and latest['pe_ratio'] and latest['pe_ratio'] > 0: pe = latest['pe_ratio'] assessment['pe_ratio'] = pe if 10 <= pe <= 25: # Reasonable P/E range health_score += 25 elif 5 <= pe < 40: # Acceptable range health_score += 15 # ROE assessment if 'roe' in latest and latest['roe']: roe = latest['roe'] assessment['roe'] = roe if roe > 0.20: # >20% ROE health_score += 25 elif roe > 0.15: # >15% ROE health_score += 20 elif roe > 0.10: # >10% ROE health_score += 15 # Debt assessment if 'debt_to_equity' in latest and latest['debt_to_equity']: debt_to_equity = latest['debt_to_equity'] assessment['debt_to_equity'] = debt_to_equity if debt_to_equity < 0.3: # Low debt health_score += 25 elif debt_to_equity < 0.6: # Moderate debt health_score += 15 elif debt_to_equity < 1.0: # High but manageable debt health_score += 5 except Exception as e: self.logger.warning(f"Error in financial health assessment: {e}") return { 'score': min(health_score, max_score), 'assessment': assessment, 'grade': self._score_to_grade(min(health_score, max_score)) } def _analyze_price_trends(self, price_data: Dict) -> Dict: """Analyze price trends and momentum""" try: prices = price_data.get('price_data', []) if len(prices) < 10: return {'trend': 'insufficient_data'} # Get closing prices closes = [p['close'] for p in prices] dates = [p['date'] for p in prices] # Calculate basic metrics current_price = closes[-1] price_52w_high = max(closes) price_52w_low = min(closes) # Calculate moving averages ma_50 = np.mean(closes[-50:]) if len(closes) >= 50 else np.mean(closes) ma_20 = np.mean(closes[-20:]) if len(closes) >= 20 else np.mean(closes) # Calculate price momentum (30-day return) month_ago_price = closes[-30] if len(closes) >= 30 else closes[0] momentum_30d = (current_price - month_ago_price) / month_ago_price # Calculate volatility (standard deviation) returns = [(closes[i] - closes[i-1]) / closes[i-1] for i in range(1, len(closes))] volatility = np.std(returns) * np.sqrt(252) # Annualized volatility # Determine trend trend = 'neutral' if current_price > ma_50 and ma_20 > ma_50: trend = 'bullish' elif current_price < ma_50 and ma_20 < ma_50: trend = 'bearish' return { 'trend': trend, 'current_price': current_price, 'price_52w_high': price_52w_high, 'price_52w_low': price_52w_low, 'from_52w_high': (current_price - price_52w_high) / price_52w_high, 'from_52w_low': (current_price - price_52w_low) / price_52w_low, 'ma_50': ma_50, 'ma_20': ma_20, 'momentum_30d': momentum_30d, 'volatility': volatility } except Exception as e: self.logger.warning(f"Error in price trend analysis: {e}") return {'trend': 'error', 'error': str(e)} def _analyze_sentiment(self, news_social_data: Dict) -> Dict: """Analyze news and social media sentiment""" try: news_articles = news_social_data.get('news', {}).get('articles', []) social_posts = news_social_data.get('social_media', {}).get('posts', []) # Simple sentiment analysis based on keywords positive_words = ['growth', 'profit', 'success', 'strong', 'beat', 'exceed', 'bullish', 'upgrade'] negative_words = ['loss', 'decline', 'fall', 'weak', 'miss', 'bearish', 'downgrade', 'concern'] total_sentiment = 0 total_items = 0 # Analyze news sentiment for article in news_articles: title = article.get('title', '').lower() summary = article.get('summary', '').lower() text = f"{title} {summary}" sentiment = 0 for word in positive_words: sentiment += text.count(word) for word in negative_words: sentiment -= text.count(word) total_sentiment += sentiment total_items += 1 # Analyze social media sentiment (including Reddit scores) social_sentiment = 0 for post in social_posts: title = post.get('title', '').lower() content = post.get('content', '').lower() text = f"{title} {content}" sentiment = 0 for word in positive_words: sentiment += text.count(word) for word in negative_words: sentiment -= text.count(word) # Factor in Reddit score if available score = post.get('score', 0) if score > 100: sentiment += 1 # High-scoring posts are generally positive elif score < -10: sentiment -= 1 social_sentiment += sentiment total_items += 1 # Calculate overall sentiment if total_items > 0: overall_sentiment = (total_sentiment + social_sentiment) / total_items else: overall_sentiment = 0 # Classify sentiment if overall_sentiment > 0.5: sentiment_label = 'positive' elif overall_sentiment < -0.5: sentiment_label = 'negative' else: sentiment_label = 'neutral' return { 'overall_sentiment': overall_sentiment, 'sentiment_label': sentiment_label, 'news_articles_count': len(news_articles), 'social_posts_count': len(social_posts), 'total_items_analyzed': total_items } except Exception as e: self.logger.warning(f"Error in sentiment analysis: {e}") return {'sentiment_label': 'error', 'error': str(e)} def _extract_key_metrics(self, financial_data: Dict) -> Dict: """Extract key financial metrics""" try: latest_data = financial_data.get('financial_data', []) if not latest_data: return {} latest = latest_data[0] return { 'revenue': latest.get('revenue'), 'net_income': latest.get('net_income'), 'eps': latest.get('eps'), 'pe_ratio': latest.get('pe_ratio'), 'pb_ratio': latest.get('pb_ratio'), 'roe': latest.get('roe'), 'roa': latest.get('roa'), 'debt_to_equity': latest.get('debt_to_equity'), 'market_cap': latest.get('market_cap'), 'period_date': latest.get('period_date') } except Exception as e: self.logger.warning(f"Error extracting key metrics: {e}") return {} def _calculate_investment_score(self, summary: Dict) -> int: """Calculate overall investment score (0-100)""" try: score = 0 # Financial health (40% weight) financial_score = summary.get('financial_health', {}).get('score', 0) score += financial_score * 0.4 # Price trends (30% weight) price_trends = summary.get('price_trends', {}) if price_trends.get('trend') == 'bullish': score += 30 elif price_trends.get('trend') == 'neutral': score += 15 # Bearish trend adds 0 # Sentiment (20% weight) sentiment = summary.get('sentiment_analysis') if sentiment: if sentiment.get('sentiment_label') == 'positive': score += 20 elif sentiment.get('sentiment_label') == 'neutral': score += 10 # Negative sentiment adds 0 # Momentum bonus (10% weight) momentum = price_trends.get('momentum_30d', 0) if momentum > 0.10: # >10% monthly return score += 10 elif momentum > 0.05: # >5% monthly return score += 7 elif momentum > 0: # Positive return score += 3 return min(int(score), 100) except Exception as e: self.logger.warning(f"Error calculating investment score: {e}") return 0 def _score_to_grade(self, score: int) -> str: """Convert numeric score to letter grade""" if score >= 90: return 'A+' elif score >= 85: return 'A' elif score >= 80: return 'A-' elif score >= 75: return 'B+' elif score >= 70: return 'B' elif score >= 65: return 'B-' elif score >= 60: return 'C+' elif score >= 55: return 'C' elif score >= 50: return 'C-' elif score >= 40: return 'D' else: return 'F' def analyze_multiple_companies(self, tickers: List[str], period: str = "3y") -> Dict: """Analyze multiple companies and return comparative data""" results = {} for ticker in tickers: self.logger.info(f"Analyzing {ticker}...") company_data = self.get_company_data(ticker, period) if company_data: results[ticker] = company_data return results def create_summary_report(self, analysis_results: Dict) -> pd.DataFrame: """Create a summary report comparing multiple companies""" summary_data = [] for ticker, data in analysis_results.items(): if not data: continue analysis = data.get('analysis_summary', {}) key_metrics = analysis.get('key_metrics', {}) financial_health = analysis.get('financial_health', {}) price_trends = analysis.get('price_trends', {}) sentiment = analysis.get('sentiment_analysis', {}) row = { 'Ticker': ticker, 'Company': data.get('company_name', ''), 'Investment Score': analysis.get('investment_score', 0), 'Financial Grade': financial_health.get('grade', 'N/A'), 'Price Trend': price_trends.get('trend', 'N/A'), 'Sentiment': sentiment.get('sentiment_label', 'N/A') if sentiment else 'N/A', # Financial metrics 'Revenue (M)': key_metrics.get('revenue', 0) / 1_000_000 if key_metrics.get('revenue') else None, 'Net Income (M)': key_metrics.get('net_income', 0) / 1_000_000 if key_metrics.get('net_income') else None, 'EPS': key_metrics.get('eps'), 'P/E Ratio': key_metrics.get('pe_ratio'), 'ROE (%)': key_metrics.get('roe') * 100 if key_metrics.get('roe') else None, 'Debt/Equity': key_metrics.get('debt_to_equity'), # Price metrics 'Current Price': price_trends.get('current_price'), '30d Momentum (%)': price_trends.get('momentum_30d', 0) * 100 if price_trends.get('momentum_30d') else None, 'Volatility (%)': price_trends.get('volatility', 0) * 100 if price_trends.get('volatility') else None, # News metrics 'News Articles': sentiment.get('news_articles_count', 0) if sentiment else 0, 'Social Posts': sentiment.get('social_posts_count', 0) if sentiment else 0, } summary_data.append(row) df = pd.DataFrame(summary_data) # Sort by investment score descending if not df.empty and 'Investment Score' in df.columns: df = df.sort_values('Investment Score', ascending=False) return df # Example usage if __name__ == "__main__": # Initialize analyzer analyzer = StockOracleAnalyzer("http://localhost:18001") # Analyze a single company print("Analyzing Apple Inc. (AAPL)...") aapl_data = analyzer.get_company_data("AAPL", period="2y") if aapl_data: print(f"Successfully retrieved data for {aapl_data['company_name']}") analysis = aapl_data['analysis_summary'] print(f"\nšŸ“Š Investment Score: {analysis['investment_score']}/100") print(f"šŸ“ˆ Financial Grade: {analysis['financial_health']['grade']}") print(f"šŸ“‰ Price Trend: {analysis['price_trends']['trend']}") if analysis['sentiment_analysis']: print(f"šŸ’­ Sentiment: {analysis['sentiment_analysis']['sentiment_label']}") print(f"\nšŸ’° Key Metrics:") metrics = analysis['key_metrics'] if metrics.get('revenue'): print(f" Revenue: ${metrics['revenue']/1e9:.2f}B") if metrics.get('net_income'): print(f" Net Income: ${metrics['net_income']/1e9:.2f}B") if metrics.get('pe_ratio'): print(f" P/E Ratio: {metrics['pe_ratio']:.2f}") if metrics.get('roe'): print(f" ROE: {metrics['roe']*100:.2f}%") # Analyze multiple companies print("\n" + "="*80) print("Analyzing multiple companies...") tickers = ["AAPL", "MSFT", "GOOGL", "TSLA", "NVDA"] results = analyzer.analyze_multiple_companies(tickers, period="1y") # Create summary report summary_df = analyzer.create_summary_report(results) if not summary_df.empty: print("\nšŸ“‹ Investment Summary Report:") print("=" * 120) # Display key columns display_cols = [ 'Ticker', 'Company', 'Investment Score', 'Financial Grade', 'Price Trend', 'Sentiment', 'P/E Ratio', 'ROE (%)', '30d Momentum (%)' ] available_cols = [col for col in display_cols if col in summary_df.columns] print(summary_df[available_cols].to_string(index=False)) # Top recommendation if 'Investment Score' in summary_df.columns: top_pick = summary_df.iloc[0] print(f"\nšŸ† Top Investment Recommendation: {top_pick['Ticker']} ({top_pick['Company']})") print(f" Investment Score: {top_pick['Investment Score']}/100") print(f" Financial Grade: {top_pick['Financial Grade']}") else: print("No data available for analysis.")