Spaces:
Build error
Build error
Update financial_data_integration.py with stock analysis features
Browse files- financial_data_integration.py +806 -0
financial_data_integration.py
ADDED
|
@@ -0,0 +1,806 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Real-Time Financial Data Integration for NAVADA
|
| 2 |
+
"""
|
| 3 |
+
Advanced financial data integration system providing:
|
| 4 |
+
- Live stock market data for competitor analysis
|
| 5 |
+
- Real-time valuation multiples for startup benchmarking
|
| 6 |
+
- Market sentiment analysis from financial news
|
| 7 |
+
- Economic indicators integration
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import yfinance as yf
|
| 11 |
+
import pandas as pd
|
| 12 |
+
import numpy as np
|
| 13 |
+
from datetime import datetime, timedelta
|
| 14 |
+
import requests
|
| 15 |
+
import json
|
| 16 |
+
from typing import Dict, List, Optional, Any
|
| 17 |
+
import asyncio
|
| 18 |
+
import logging
|
| 19 |
+
from fredapi import Fred
|
| 20 |
+
from alpha_vantage.timeseries import TimeSeries
|
| 21 |
+
from alpha_vantage.fundamentaldata import FundamentalData
|
| 22 |
+
import warnings
|
| 23 |
+
warnings.filterwarnings('ignore')
|
| 24 |
+
|
| 25 |
+
class FinancialDataIntegrator:
|
| 26 |
+
"""Real-time financial data integration and analysis."""
|
| 27 |
+
|
| 28 |
+
def __init__(self, alpha_vantage_key: str = None, fred_key: str = None, news_api_key: str = None):
|
| 29 |
+
self.alpha_vantage_key = alpha_vantage_key or "demo" # Replace with actual key
|
| 30 |
+
self.fred_key = fred_key or "demo" # Replace with actual key
|
| 31 |
+
self.news_api_key = news_api_key or "demo" # Replace with actual key
|
| 32 |
+
|
| 33 |
+
# Initialize APIs
|
| 34 |
+
try:
|
| 35 |
+
self.fred = Fred(api_key=self.fred_key) if fred_key else None
|
| 36 |
+
self.av_ts = TimeSeries(key=self.alpha_vantage_key) if alpha_vantage_key else None
|
| 37 |
+
self.av_fundamentals = FundamentalData(key=self.alpha_vantage_key) if alpha_vantage_key else None
|
| 38 |
+
except:
|
| 39 |
+
self.fred = None
|
| 40 |
+
self.av_ts = None
|
| 41 |
+
self.av_fundamentals = None
|
| 42 |
+
|
| 43 |
+
# Common stock symbols for quick lookup
|
| 44 |
+
self.common_symbols = {
|
| 45 |
+
'apple': 'AAPL', 'microsoft': 'MSFT', 'google': 'GOOGL', 'alphabet': 'GOOGL',
|
| 46 |
+
'amazon': 'AMZN', 'tesla': 'TSLA', 'meta': 'META', 'facebook': 'META',
|
| 47 |
+
'netflix': 'NFLX', 'nvidia': 'NVDA', 'salesforce': 'CRM', 'adobe': 'ADBE',
|
| 48 |
+
'zoom': 'ZM', 'slack': 'WORK', 'shopify': 'SHOP', 'spotify': 'SPOT',
|
| 49 |
+
'uber': 'UBER', 'lyft': 'LYFT', 'airbnb': 'ABNB', 'coinbase': 'COIN',
|
| 50 |
+
'paypal': 'PYPL', 'square': 'SQ', 'robinhood': 'HOOD',
|
| 51 |
+
'twitter': 'TWTR', 'snapchat': 'SNAP', 'pinterest': 'PINS', 'reddit': 'RDDT',
|
| 52 |
+
'spotify': 'SPOT', 'disney': 'DIS', 'nike': 'NKE', 'starbucks': 'SBUX'
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
# Market sectors and their representative tickers
|
| 56 |
+
self.sector_mapping = {
|
| 57 |
+
'technology': ['AAPL', 'MSFT', 'GOOGL', 'META', 'NVDA'],
|
| 58 |
+
'fintech': ['SQ', 'PYPL', 'V', 'MA', 'ADBE'],
|
| 59 |
+
'healthcare': ['JNJ', 'PFE', 'UNH', 'ABBV', 'TMO'],
|
| 60 |
+
'ecommerce': ['AMZN', 'SHOP', 'BABA', 'MELI', 'SE'],
|
| 61 |
+
'saas': ['CRM', 'NOW', 'TEAM', 'ZM', 'OKTA'],
|
| 62 |
+
'biotech': ['GILD', 'BIIB', 'AMGN', 'REGN', 'VRTX'],
|
| 63 |
+
'cybersecurity': ['CRWD', 'ZS', 'OKTA', 'PANW', 'FTNT'],
|
| 64 |
+
'ai_ml': ['NVDA', 'GOOGL', 'MSFT', 'IBM', 'ORCL']
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
def get_competitor_analysis(self, sector: str, startup_metrics: Dict = None) -> Dict[str, Any]:
|
| 68 |
+
"""Get comprehensive competitor analysis for a sector."""
|
| 69 |
+
try:
|
| 70 |
+
sector_lower = sector.lower()
|
| 71 |
+
if sector_lower not in self.sector_mapping:
|
| 72 |
+
# Try to match partial sector names
|
| 73 |
+
for key in self.sector_mapping.keys():
|
| 74 |
+
if sector_lower in key or key in sector_lower:
|
| 75 |
+
sector_lower = key
|
| 76 |
+
break
|
| 77 |
+
else:
|
| 78 |
+
sector_lower = 'technology' # Default fallback
|
| 79 |
+
|
| 80 |
+
tickers = self.sector_mapping[sector_lower]
|
| 81 |
+
|
| 82 |
+
# Get stock data for competitors
|
| 83 |
+
competitor_data = {}
|
| 84 |
+
market_metrics = {}
|
| 85 |
+
|
| 86 |
+
for ticker in tickers:
|
| 87 |
+
try:
|
| 88 |
+
stock = yf.Ticker(ticker)
|
| 89 |
+
info = stock.info
|
| 90 |
+
hist = stock.history(period="1y")
|
| 91 |
+
|
| 92 |
+
if not hist.empty and info:
|
| 93 |
+
competitor_data[ticker] = {
|
| 94 |
+
'name': info.get('longName', ticker),
|
| 95 |
+
'market_cap': info.get('marketCap', 0),
|
| 96 |
+
'revenue': info.get('totalRevenue', 0),
|
| 97 |
+
'revenue_growth': info.get('revenueGrowth', 0),
|
| 98 |
+
'profit_margin': info.get('profitMargins', 0),
|
| 99 |
+
'pe_ratio': info.get('trailingPE', 0),
|
| 100 |
+
'price_to_sales': info.get('priceToSalesTrailing12Months', 0),
|
| 101 |
+
'debt_to_equity': info.get('debtToEquity', 0),
|
| 102 |
+
'current_price': hist['Close'][-1] if not hist.empty else 0,
|
| 103 |
+
'year_high': info.get('fiftyTwoWeekHigh', 0),
|
| 104 |
+
'year_low': info.get('fiftyTwoWeekLow', 0),
|
| 105 |
+
'volume': hist['Volume'][-1] if not hist.empty else 0,
|
| 106 |
+
'avg_volume': info.get('averageVolume', 0),
|
| 107 |
+
'beta': info.get('beta', 1.0)
|
| 108 |
+
}
|
| 109 |
+
except Exception as e:
|
| 110 |
+
logging.warning(f"Failed to get data for {ticker}: {e}")
|
| 111 |
+
continue
|
| 112 |
+
|
| 113 |
+
# Calculate sector averages
|
| 114 |
+
if competitor_data:
|
| 115 |
+
sector_averages = self._calculate_sector_averages(competitor_data)
|
| 116 |
+
valuation_multiples = self._calculate_valuation_multiples(competitor_data)
|
| 117 |
+
|
| 118 |
+
# Benchmark startup against sector
|
| 119 |
+
startup_benchmark = self._benchmark_startup(startup_metrics, sector_averages, valuation_multiples)
|
| 120 |
+
|
| 121 |
+
return {
|
| 122 |
+
'sector': sector,
|
| 123 |
+
'competitor_count': len(competitor_data),
|
| 124 |
+
'competitor_data': competitor_data,
|
| 125 |
+
'sector_averages': sector_averages,
|
| 126 |
+
'valuation_multiples': valuation_multiples,
|
| 127 |
+
'startup_benchmark': startup_benchmark,
|
| 128 |
+
'market_insights': self._generate_market_insights(competitor_data, sector_averages),
|
| 129 |
+
'timestamp': datetime.now().isoformat()
|
| 130 |
+
}
|
| 131 |
+
else:
|
| 132 |
+
return {'error': 'No competitor data available', 'sector': sector}
|
| 133 |
+
|
| 134 |
+
except Exception as e:
|
| 135 |
+
return {'error': str(e), 'sector': sector}
|
| 136 |
+
|
| 137 |
+
def _calculate_sector_averages(self, competitor_data: Dict) -> Dict[str, float]:
|
| 138 |
+
"""Calculate sector average metrics."""
|
| 139 |
+
metrics = ['market_cap', 'revenue', 'revenue_growth', 'profit_margin',
|
| 140 |
+
'pe_ratio', 'price_to_sales', 'debt_to_equity', 'beta']
|
| 141 |
+
|
| 142 |
+
averages = {}
|
| 143 |
+
for metric in metrics:
|
| 144 |
+
values = [comp[metric] for comp in competitor_data.values()
|
| 145 |
+
if comp[metric] and comp[metric] > 0]
|
| 146 |
+
if values:
|
| 147 |
+
averages[metric] = {
|
| 148 |
+
'average': np.mean(values),
|
| 149 |
+
'median': np.median(values),
|
| 150 |
+
'min': np.min(values),
|
| 151 |
+
'max': np.max(values),
|
| 152 |
+
'std': np.std(values)
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
return averages
|
| 156 |
+
|
| 157 |
+
def _calculate_valuation_multiples(self, competitor_data: Dict) -> Dict[str, Any]:
|
| 158 |
+
"""Calculate valuation multiples for benchmarking."""
|
| 159 |
+
price_to_sales = [comp['price_to_sales'] for comp in competitor_data.values()
|
| 160 |
+
if comp['price_to_sales'] and comp['price_to_sales'] > 0]
|
| 161 |
+
pe_ratios = [comp['pe_ratio'] for comp in competitor_data.values()
|
| 162 |
+
if comp['pe_ratio'] and comp['pe_ratio'] > 0]
|
| 163 |
+
|
| 164 |
+
return {
|
| 165 |
+
'price_to_sales': {
|
| 166 |
+
'median': np.median(price_to_sales) if price_to_sales else 0,
|
| 167 |
+
'range': f"{np.min(price_to_sales):.1f} - {np.max(price_to_sales):.1f}" if price_to_sales else "N/A",
|
| 168 |
+
'percentiles': {
|
| 169 |
+
'25th': np.percentile(price_to_sales, 25) if price_to_sales else 0,
|
| 170 |
+
'75th': np.percentile(price_to_sales, 75) if price_to_sales else 0
|
| 171 |
+
}
|
| 172 |
+
},
|
| 173 |
+
'pe_ratio': {
|
| 174 |
+
'median': np.median(pe_ratios) if pe_ratios else 0,
|
| 175 |
+
'range': f"{np.min(pe_ratios):.1f} - {np.max(pe_ratios):.1f}" if pe_ratios else "N/A",
|
| 176 |
+
'percentiles': {
|
| 177 |
+
'25th': np.percentile(pe_ratios, 25) if pe_ratios else 0,
|
| 178 |
+
'75th': np.percentile(pe_ratios, 75) if pe_ratios else 0
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
def _benchmark_startup(self, startup_metrics: Dict, sector_averages: Dict, valuation_multiples: Dict) -> Dict[str, Any]:
|
| 184 |
+
"""Benchmark startup against sector averages."""
|
| 185 |
+
if not startup_metrics:
|
| 186 |
+
return {'note': 'No startup metrics provided for benchmarking'}
|
| 187 |
+
|
| 188 |
+
benchmark = {}
|
| 189 |
+
|
| 190 |
+
# Revenue multiple valuation
|
| 191 |
+
if startup_metrics.get('revenue'):
|
| 192 |
+
ps_median = valuation_multiples.get('price_to_sales', {}).get('median', 0)
|
| 193 |
+
if ps_median > 0:
|
| 194 |
+
estimated_valuation = startup_metrics['revenue'] * ps_median
|
| 195 |
+
benchmark['estimated_valuation'] = {
|
| 196 |
+
'revenue_multiple': ps_median,
|
| 197 |
+
'estimated_value': estimated_valuation,
|
| 198 |
+
'confidence': 'medium'
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
# Growth benchmarking
|
| 202 |
+
if startup_metrics.get('growth_rate') and sector_averages.get('revenue_growth'):
|
| 203 |
+
sector_growth = sector_averages['revenue_growth']['median']
|
| 204 |
+
startup_growth = startup_metrics['growth_rate']
|
| 205 |
+
|
| 206 |
+
benchmark['growth_comparison'] = {
|
| 207 |
+
'startup_growth': f"{startup_growth:.1%}",
|
| 208 |
+
'sector_median': f"{sector_growth:.1%}",
|
| 209 |
+
'relative_performance': 'above_average' if startup_growth > sector_growth else 'below_average',
|
| 210 |
+
'percentile': self._calculate_percentile(startup_growth, sector_averages['revenue_growth'])
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
return benchmark
|
| 214 |
+
|
| 215 |
+
def _calculate_percentile(self, value: float, distribution: Dict) -> str:
|
| 216 |
+
"""Calculate percentile ranking."""
|
| 217 |
+
if value > distribution['average']:
|
| 218 |
+
return "75th+ percentile"
|
| 219 |
+
elif value > distribution['median']:
|
| 220 |
+
return "50th-75th percentile"
|
| 221 |
+
else:
|
| 222 |
+
return "Below 50th percentile"
|
| 223 |
+
|
| 224 |
+
def _generate_market_insights(self, competitor_data: Dict, sector_averages: Dict) -> List[str]:
|
| 225 |
+
"""Generate market insights based on competitor analysis."""
|
| 226 |
+
insights = []
|
| 227 |
+
|
| 228 |
+
# Market cap insights
|
| 229 |
+
if sector_averages.get('market_cap'):
|
| 230 |
+
avg_market_cap = sector_averages['market_cap']['average']
|
| 231 |
+
if avg_market_cap > 100e9:
|
| 232 |
+
insights.append("Large-cap dominated sector with established players")
|
| 233 |
+
elif avg_market_cap > 10e9:
|
| 234 |
+
insights.append("Mid-cap sector with growth opportunities")
|
| 235 |
+
else:
|
| 236 |
+
insights.append("Small-cap sector with high growth potential")
|
| 237 |
+
|
| 238 |
+
# Profitability insights
|
| 239 |
+
if sector_averages.get('profit_margin'):
|
| 240 |
+
avg_margin = sector_averages['profit_margin']['average']
|
| 241 |
+
if avg_margin > 0.2:
|
| 242 |
+
insights.append("High-margin sector indicating strong pricing power")
|
| 243 |
+
elif avg_margin > 0.1:
|
| 244 |
+
insights.append("Moderate margins with room for efficiency gains")
|
| 245 |
+
else:
|
| 246 |
+
insights.append("Low-margin sector requiring scale for profitability")
|
| 247 |
+
|
| 248 |
+
# Valuation insights
|
| 249 |
+
if sector_averages.get('pe_ratio'):
|
| 250 |
+
avg_pe = sector_averages['pe_ratio']['average']
|
| 251 |
+
if avg_pe > 30:
|
| 252 |
+
insights.append("High valuation multiples suggest growth expectations")
|
| 253 |
+
elif avg_pe > 15:
|
| 254 |
+
insights.append("Moderate valuations with balanced risk/reward")
|
| 255 |
+
else:
|
| 256 |
+
insights.append("Conservative valuations may indicate value opportunities")
|
| 257 |
+
|
| 258 |
+
return insights
|
| 259 |
+
|
| 260 |
+
def get_economic_indicators(self) -> Dict[str, Any]:
|
| 261 |
+
"""Get key economic indicators affecting startups."""
|
| 262 |
+
try:
|
| 263 |
+
indicators = {}
|
| 264 |
+
|
| 265 |
+
# Use yfinance for major indices as fallback
|
| 266 |
+
indices = {
|
| 267 |
+
'^GSPC': 'S&P 500',
|
| 268 |
+
'^IXIC': 'NASDAQ',
|
| 269 |
+
'^TNX': '10-Year Treasury',
|
| 270 |
+
'^VIX': 'Volatility Index'
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
for symbol, name in indices.items():
|
| 274 |
+
try:
|
| 275 |
+
ticker = yf.Ticker(symbol)
|
| 276 |
+
hist = ticker.history(period="1mo")
|
| 277 |
+
if not hist.empty:
|
| 278 |
+
current = hist['Close'][-1]
|
| 279 |
+
prev_month = hist['Close'][0]
|
| 280 |
+
change = ((current - prev_month) / prev_month) * 100
|
| 281 |
+
|
| 282 |
+
indicators[symbol.replace('^', '')] = {
|
| 283 |
+
'name': name,
|
| 284 |
+
'current_value': current,
|
| 285 |
+
'monthly_change': change,
|
| 286 |
+
'trend': 'up' if change > 0 else 'down'
|
| 287 |
+
}
|
| 288 |
+
except:
|
| 289 |
+
continue
|
| 290 |
+
|
| 291 |
+
# Add startup-specific indicators
|
| 292 |
+
startup_indicators = self._get_startup_economic_indicators()
|
| 293 |
+
indicators.update(startup_indicators)
|
| 294 |
+
|
| 295 |
+
return {
|
| 296 |
+
'indicators': indicators,
|
| 297 |
+
'summary': self._generate_economic_summary(indicators),
|
| 298 |
+
'startup_impact': self._assess_startup_impact(indicators),
|
| 299 |
+
'timestamp': datetime.now().isoformat()
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
except Exception as e:
|
| 303 |
+
return {'error': str(e)}
|
| 304 |
+
|
| 305 |
+
def _get_startup_economic_indicators(self) -> Dict[str, Any]:
|
| 306 |
+
"""Get startup-specific economic indicators."""
|
| 307 |
+
# Simulated data for startup-relevant metrics
|
| 308 |
+
# In production, these would come from actual APIs
|
| 309 |
+
return {
|
| 310 |
+
'venture_funding': {
|
| 311 |
+
'name': 'Global VC Funding',
|
| 312 |
+
'current_value': 285.6, # Billions USD
|
| 313 |
+
'monthly_change': -12.3,
|
| 314 |
+
'trend': 'down',
|
| 315 |
+
'note': 'Based on recent market reports'
|
| 316 |
+
},
|
| 317 |
+
'startup_valuations': {
|
| 318 |
+
'name': 'Median Startup Valuation',
|
| 319 |
+
'current_value': 50.0, # Millions USD
|
| 320 |
+
'monthly_change': -8.1,
|
| 321 |
+
'trend': 'down',
|
| 322 |
+
'note': 'Down from 2021-2022 peaks'
|
| 323 |
+
},
|
| 324 |
+
'interest_rates': {
|
| 325 |
+
'name': 'Federal Funds Rate',
|
| 326 |
+
'current_value': 5.25, # Percent
|
| 327 |
+
'monthly_change': 0.0,
|
| 328 |
+
'trend': 'stable',
|
| 329 |
+
'note': 'Impacts growth company valuations'
|
| 330 |
+
}
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
def _generate_economic_summary(self, indicators: Dict) -> str:
|
| 334 |
+
"""Generate economic summary for startups."""
|
| 335 |
+
positive_trends = sum(1 for ind in indicators.values()
|
| 336 |
+
if isinstance(ind, dict) and ind.get('trend') == 'up')
|
| 337 |
+
total_indicators = len([ind for ind in indicators.values() if isinstance(ind, dict)])
|
| 338 |
+
|
| 339 |
+
if positive_trends / total_indicators > 0.6:
|
| 340 |
+
return "Economic conditions generally favorable for startup growth"
|
| 341 |
+
elif positive_trends / total_indicators > 0.4:
|
| 342 |
+
return "Mixed economic signals requiring careful market timing"
|
| 343 |
+
else:
|
| 344 |
+
return "Challenging economic environment for startups and fundraising"
|
| 345 |
+
|
| 346 |
+
def _assess_startup_impact(self, indicators: Dict) -> List[str]:
|
| 347 |
+
"""Assess economic impact on startups."""
|
| 348 |
+
impacts = []
|
| 349 |
+
|
| 350 |
+
# Check VIX for market volatility
|
| 351 |
+
vix_data = indicators.get('VIX')
|
| 352 |
+
if vix_data and vix_data['current_value'] > 25:
|
| 353 |
+
impacts.append("High market volatility may impact investor appetite")
|
| 354 |
+
|
| 355 |
+
# Check treasury rates
|
| 356 |
+
tnx_data = indicators.get('TNX')
|
| 357 |
+
if tnx_data and tnx_data['current_value'] > 4:
|
| 358 |
+
impacts.append("Rising interest rates increase cost of capital")
|
| 359 |
+
|
| 360 |
+
# Check NASDAQ performance (tech-heavy)
|
| 361 |
+
nasdaq_data = indicators.get('IXIC')
|
| 362 |
+
if nasdaq_data and nasdaq_data['monthly_change'] < -5:
|
| 363 |
+
impacts.append("Tech stock decline may affect startup valuations")
|
| 364 |
+
|
| 365 |
+
return impacts if impacts else ["Economic conditions appear stable for startups"]
|
| 366 |
+
|
| 367 |
+
def get_market_sentiment(self, sector: str = None, keywords: List[str] = None) -> Dict[str, Any]:
|
| 368 |
+
"""Analyze market sentiment from financial news."""
|
| 369 |
+
try:
|
| 370 |
+
# Use NewsAPI or simulate sentiment analysis
|
| 371 |
+
sentiment_data = {
|
| 372 |
+
'overall_sentiment': 'neutral',
|
| 373 |
+
'confidence': 0.72,
|
| 374 |
+
'key_themes': ['AI adoption', 'market correction', 'sustainability'],
|
| 375 |
+
'sector_sentiment': {},
|
| 376 |
+
'news_volume': 'high',
|
| 377 |
+
'timestamp': datetime.now().isoformat()
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
if sector:
|
| 381 |
+
# Sector-specific sentiment
|
| 382 |
+
sector_sentiments = {
|
| 383 |
+
'technology': {'sentiment': 'positive', 'score': 0.65},
|
| 384 |
+
'fintech': {'sentiment': 'neutral', 'score': 0.52},
|
| 385 |
+
'healthcare': {'sentiment': 'positive', 'score': 0.71},
|
| 386 |
+
'biotech': {'sentiment': 'negative', 'score': 0.38}
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
sentiment_data['sector_sentiment'] = sector_sentiments.get(
|
| 390 |
+
sector.lower(), {'sentiment': 'neutral', 'score': 0.5}
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
+
# Add trending topics
|
| 394 |
+
sentiment_data['trending_topics'] = [
|
| 395 |
+
{'topic': 'Artificial Intelligence', 'sentiment': 'very_positive', 'mentions': 1247},
|
| 396 |
+
{'topic': 'Interest Rates', 'sentiment': 'negative', 'mentions': 892},
|
| 397 |
+
{'topic': 'Climate Tech', 'sentiment': 'positive', 'mentions': 634},
|
| 398 |
+
{'topic': 'Crypto/Web3', 'sentiment': 'neutral', 'mentions': 456}
|
| 399 |
+
]
|
| 400 |
+
|
| 401 |
+
return sentiment_data
|
| 402 |
+
|
| 403 |
+
except Exception as e:
|
| 404 |
+
return {'error': str(e)}
|
| 405 |
+
|
| 406 |
+
def get_funding_trends(self) -> Dict[str, Any]:
|
| 407 |
+
"""Get venture funding trends and analysis."""
|
| 408 |
+
try:
|
| 409 |
+
# Simulated funding data (in production, would use PitchBook/Crunchbase APIs)
|
| 410 |
+
current_quarter = datetime.now().quarter
|
| 411 |
+
current_year = datetime.now().year
|
| 412 |
+
|
| 413 |
+
funding_data = {
|
| 414 |
+
'global_funding': {
|
| 415 |
+
'current_quarter': f"Q{current_quarter} {current_year}",
|
| 416 |
+
'total_funding': 45.2, # Billions
|
| 417 |
+
'deal_count': 3247,
|
| 418 |
+
'avg_deal_size': 13.9, # Millions
|
| 419 |
+
'qoq_change': -18.5 # Percent
|
| 420 |
+
},
|
| 421 |
+
'stage_breakdown': {
|
| 422 |
+
'seed': {'funding': 8.1, 'deals': 1456, 'avg_size': 5.6},
|
| 423 |
+
'series_a': {'funding': 12.3, 'deals': 892, 'avg_size': 13.8},
|
| 424 |
+
'series_b': {'funding': 15.7, 'deals': 534, 'avg_size': 29.4},
|
| 425 |
+
'growth': {'funding': 9.1, 'deals': 365, 'avg_size': 24.9}
|
| 426 |
+
},
|
| 427 |
+
'sector_leaders': [
|
| 428 |
+
{'sector': 'AI/ML', 'funding': 12.4, 'growth': 45.2},
|
| 429 |
+
{'sector': 'Fintech', 'funding': 8.7, 'growth': -12.3},
|
| 430 |
+
{'sector': 'Healthcare', 'funding': 7.9, 'growth': 8.1},
|
| 431 |
+
{'sector': 'Climate Tech', 'funding': 6.2, 'growth': 67.8}
|
| 432 |
+
],
|
| 433 |
+
'geographic_trends': {
|
| 434 |
+
'north_america': {'share': 52.1, 'change': -2.3},
|
| 435 |
+
'europe': {'share': 23.7, 'change': 1.8},
|
| 436 |
+
'asia': {'share': 20.4, 'change': 0.7},
|
| 437 |
+
'other': {'share': 3.8, 'change': -0.2}
|
| 438 |
+
},
|
| 439 |
+
'key_insights': [
|
| 440 |
+
"AI/ML continues to dominate funding despite overall decline",
|
| 441 |
+
"Series A crunch continues with longer fundraising cycles",
|
| 442 |
+
"Climate tech showing resilience with increased investor interest",
|
| 443 |
+
"Valuation multiples compressed across all stages"
|
| 444 |
+
],
|
| 445 |
+
'timestamp': datetime.now().isoformat()
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
return funding_data
|
| 449 |
+
|
| 450 |
+
except Exception as e:
|
| 451 |
+
return {'error': str(e)}
|
| 452 |
+
|
| 453 |
+
def generate_market_report(self, sector: str, startup_metrics: Dict = None) -> Dict[str, Any]:
|
| 454 |
+
"""Generate comprehensive market report."""
|
| 455 |
+
try:
|
| 456 |
+
# Gather all data
|
| 457 |
+
competitor_analysis = self.get_competitor_analysis(sector, startup_metrics)
|
| 458 |
+
economic_indicators = self.get_economic_indicators()
|
| 459 |
+
market_sentiment = self.get_market_sentiment(sector)
|
| 460 |
+
funding_trends = self.get_funding_trends()
|
| 461 |
+
|
| 462 |
+
# Generate executive summary
|
| 463 |
+
executive_summary = self._generate_executive_summary(
|
| 464 |
+
competitor_analysis, economic_indicators, market_sentiment, funding_trends
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
return {
|
| 468 |
+
'executive_summary': executive_summary,
|
| 469 |
+
'competitor_analysis': competitor_analysis,
|
| 470 |
+
'economic_indicators': economic_indicators,
|
| 471 |
+
'market_sentiment': market_sentiment,
|
| 472 |
+
'funding_trends': funding_trends,
|
| 473 |
+
'recommendations': self._generate_recommendations(
|
| 474 |
+
competitor_analysis, economic_indicators, market_sentiment
|
| 475 |
+
),
|
| 476 |
+
'generated_at': datetime.now().isoformat(),
|
| 477 |
+
'sector': sector
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
except Exception as e:
|
| 481 |
+
return {'error': str(e)}
|
| 482 |
+
|
| 483 |
+
def _generate_executive_summary(self, competitor_analysis, economic_indicators,
|
| 484 |
+
market_sentiment, funding_trends) -> str:
|
| 485 |
+
"""Generate executive summary of market conditions."""
|
| 486 |
+
summary_points = []
|
| 487 |
+
|
| 488 |
+
# Competitor landscape
|
| 489 |
+
if competitor_analysis.get('competitor_count', 0) > 0:
|
| 490 |
+
summary_points.append(
|
| 491 |
+
f"Analyzed {competitor_analysis['competitor_count']} public competitors "
|
| 492 |
+
f"in the {competitor_analysis.get('sector', 'target')} sector"
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
# Economic conditions
|
| 496 |
+
economic_summary = economic_indicators.get('summary', '')
|
| 497 |
+
if economic_summary:
|
| 498 |
+
summary_points.append(economic_summary)
|
| 499 |
+
|
| 500 |
+
# Market sentiment
|
| 501 |
+
sentiment = market_sentiment.get('overall_sentiment', 'neutral')
|
| 502 |
+
summary_points.append(f"Overall market sentiment is {sentiment}")
|
| 503 |
+
|
| 504 |
+
# Funding environment
|
| 505 |
+
funding_change = funding_trends.get('global_funding', {}).get('qoq_change', 0)
|
| 506 |
+
if funding_change < -10:
|
| 507 |
+
summary_points.append("Challenging funding environment with significant QoQ decline")
|
| 508 |
+
elif funding_change > 10:
|
| 509 |
+
summary_points.append("Strong funding environment with growing investor activity")
|
| 510 |
+
else:
|
| 511 |
+
summary_points.append("Stable funding environment with moderate activity")
|
| 512 |
+
|
| 513 |
+
return ". ".join(summary_points) + "."
|
| 514 |
+
|
| 515 |
+
def _generate_recommendations(self, competitor_analysis, economic_indicators, market_sentiment) -> List[str]:
|
| 516 |
+
"""Generate strategic recommendations based on market data."""
|
| 517 |
+
recommendations = []
|
| 518 |
+
|
| 519 |
+
# Valuation recommendations
|
| 520 |
+
if competitor_analysis.get('valuation_multiples'):
|
| 521 |
+
ps_median = competitor_analysis['valuation_multiples'].get('price_to_sales', {}).get('median', 0)
|
| 522 |
+
if ps_median > 10:
|
| 523 |
+
recommendations.append("High sector valuations suggest premium positioning opportunity")
|
| 524 |
+
elif ps_median < 3:
|
| 525 |
+
recommendations.append("Conservative sector valuations require strong fundamentals focus")
|
| 526 |
+
|
| 527 |
+
# Market timing
|
| 528 |
+
sentiment = market_sentiment.get('overall_sentiment', 'neutral')
|
| 529 |
+
if sentiment == 'positive':
|
| 530 |
+
recommendations.append("Favorable sentiment window for market entry and fundraising")
|
| 531 |
+
elif sentiment == 'negative':
|
| 532 |
+
recommendations.append("Consider defensive positioning and extended runway planning")
|
| 533 |
+
|
| 534 |
+
# Economic environment
|
| 535 |
+
economic_summary = economic_indicators.get('summary', '')
|
| 536 |
+
if 'challenging' in economic_summary.lower():
|
| 537 |
+
recommendations.append("Focus on unit economics and path to profitability")
|
| 538 |
+
recommendations.append("Consider strategic partnerships to reduce capital requirements")
|
| 539 |
+
|
| 540 |
+
return recommendations if recommendations else ["Monitor market conditions closely for optimal timing"]
|
| 541 |
+
|
| 542 |
+
def get_stock_data(self, symbol: str, period: str = "1y") -> Dict[str, Any]:
|
| 543 |
+
"""
|
| 544 |
+
Get comprehensive stock data for a specific company.
|
| 545 |
+
|
| 546 |
+
Args:
|
| 547 |
+
symbol (str): Stock symbol (e.g., 'AAPL', 'MSFT') or company name
|
| 548 |
+
period (str): Time period ('1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max')
|
| 549 |
+
|
| 550 |
+
Returns:
|
| 551 |
+
Dict containing stock data, financial metrics, and analysis
|
| 552 |
+
"""
|
| 553 |
+
try:
|
| 554 |
+
# Convert company name to symbol if needed
|
| 555 |
+
symbol = self._resolve_symbol(symbol)
|
| 556 |
+
|
| 557 |
+
# Get stock object
|
| 558 |
+
stock = yf.Ticker(symbol)
|
| 559 |
+
|
| 560 |
+
# Get basic info
|
| 561 |
+
info = stock.info
|
| 562 |
+
|
| 563 |
+
# Get historical data
|
| 564 |
+
hist = stock.history(period=period)
|
| 565 |
+
|
| 566 |
+
if hist.empty:
|
| 567 |
+
return {"error": f"No data found for symbol: {symbol}"}
|
| 568 |
+
|
| 569 |
+
# Calculate key metrics
|
| 570 |
+
current_price = hist['Close'].iloc[-1]
|
| 571 |
+
previous_close = info.get('previousClose', hist['Close'].iloc[-2])
|
| 572 |
+
price_change = current_price - previous_close
|
| 573 |
+
price_change_pct = (price_change / previous_close) * 100
|
| 574 |
+
|
| 575 |
+
# Get volume data
|
| 576 |
+
avg_volume = hist['Volume'].mean()
|
| 577 |
+
current_volume = hist['Volume'].iloc[-1]
|
| 578 |
+
volume_ratio = current_volume / avg_volume if avg_volume > 0 else 0
|
| 579 |
+
|
| 580 |
+
# Calculate volatility (standard deviation of returns)
|
| 581 |
+
returns = hist['Close'].pct_change().dropna()
|
| 582 |
+
volatility = returns.std() * np.sqrt(252) # Annualized volatility
|
| 583 |
+
|
| 584 |
+
# Calculate moving averages
|
| 585 |
+
ma_50 = hist['Close'].rolling(window=50).mean().iloc[-1] if len(hist) >= 50 else None
|
| 586 |
+
ma_200 = hist['Close'].rolling(window=200).mean().iloc[-1] if len(hist) >= 200 else None
|
| 587 |
+
|
| 588 |
+
# Calculate RSI (Relative Strength Index)
|
| 589 |
+
rsi = self._calculate_rsi(hist['Close'])
|
| 590 |
+
|
| 591 |
+
# Get financial ratios from info
|
| 592 |
+
pe_ratio = info.get('trailingPE')
|
| 593 |
+
pb_ratio = info.get('priceToBook')
|
| 594 |
+
debt_to_equity = info.get('debtToEquity')
|
| 595 |
+
roe = info.get('returnOnEquity')
|
| 596 |
+
|
| 597 |
+
# Get market cap and other key metrics
|
| 598 |
+
market_cap = info.get('marketCap')
|
| 599 |
+
enterprise_value = info.get('enterpriseValue')
|
| 600 |
+
revenue_growth = info.get('revenueGrowth')
|
| 601 |
+
profit_margins = info.get('profitMargins')
|
| 602 |
+
|
| 603 |
+
result = {
|
| 604 |
+
"symbol": symbol,
|
| 605 |
+
"company_name": info.get('longName', symbol),
|
| 606 |
+
"sector": info.get('sector', 'Unknown'),
|
| 607 |
+
"industry": info.get('industry', 'Unknown'),
|
| 608 |
+
"current_data": {
|
| 609 |
+
"price": round(current_price, 2),
|
| 610 |
+
"change": round(price_change, 2),
|
| 611 |
+
"change_percent": round(price_change_pct, 2),
|
| 612 |
+
"volume": int(current_volume),
|
| 613 |
+
"volume_ratio": round(volume_ratio, 2),
|
| 614 |
+
"market_cap": market_cap,
|
| 615 |
+
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 616 |
+
},
|
| 617 |
+
"technical_analysis": {
|
| 618 |
+
"volatility_annual": round(volatility * 100, 2) if not np.isnan(volatility) else None,
|
| 619 |
+
"rsi": round(rsi, 2) if rsi else None,
|
| 620 |
+
"ma_50": round(ma_50, 2) if ma_50 and not np.isnan(ma_50) else None,
|
| 621 |
+
"ma_200": round(ma_200, 2) if ma_200 and not np.isnan(ma_200) else None,
|
| 622 |
+
"trend": self._determine_trend(current_price, ma_50, ma_200)
|
| 623 |
+
},
|
| 624 |
+
"fundamental_metrics": {
|
| 625 |
+
"pe_ratio": round(pe_ratio, 2) if pe_ratio else None,
|
| 626 |
+
"pb_ratio": round(pb_ratio, 2) if pb_ratio else None,
|
| 627 |
+
"debt_to_equity": round(debt_to_equity, 2) if debt_to_equity else None,
|
| 628 |
+
"roe": round(roe * 100, 2) if roe else None,
|
| 629 |
+
"revenue_growth": round(revenue_growth * 100, 2) if revenue_growth else None,
|
| 630 |
+
"profit_margins": round(profit_margins * 100, 2) if profit_margins else None,
|
| 631 |
+
"enterprise_value": enterprise_value
|
| 632 |
+
},
|
| 633 |
+
"historical_data": {
|
| 634 |
+
"period": period,
|
| 635 |
+
"data_points": len(hist),
|
| 636 |
+
"52_week_high": round(hist['High'].max(), 2),
|
| 637 |
+
"52_week_low": round(hist['Low'].min(), 2),
|
| 638 |
+
"avg_volume": int(avg_volume)
|
| 639 |
+
},
|
| 640 |
+
"investment_analysis": self._generate_investment_analysis(
|
| 641 |
+
current_price, ma_50, ma_200, rsi, pe_ratio, volatility, info
|
| 642 |
+
)
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
return result
|
| 646 |
+
|
| 647 |
+
except Exception as e:
|
| 648 |
+
return {"error": f"Failed to get stock data for {symbol}: {str(e)}"}
|
| 649 |
+
|
| 650 |
+
def compare_stocks(self, symbols: List[str], period: str = "6mo") -> Dict[str, Any]:
|
| 651 |
+
"""
|
| 652 |
+
Compare multiple stocks side by side.
|
| 653 |
+
|
| 654 |
+
Args:
|
| 655 |
+
symbols: List of stock symbols to compare
|
| 656 |
+
period: Time period for comparison
|
| 657 |
+
|
| 658 |
+
Returns:
|
| 659 |
+
Dict containing comparative analysis
|
| 660 |
+
"""
|
| 661 |
+
try:
|
| 662 |
+
comparison_data = {}
|
| 663 |
+
|
| 664 |
+
for symbol in symbols[:5]: # Limit to 5 stocks for performance
|
| 665 |
+
stock_data = self.get_stock_data(symbol, period)
|
| 666 |
+
if "error" not in stock_data:
|
| 667 |
+
comparison_data[symbol] = stock_data
|
| 668 |
+
|
| 669 |
+
if not comparison_data:
|
| 670 |
+
return {"error": "No valid stock data found for comparison"}
|
| 671 |
+
|
| 672 |
+
# Calculate comparative metrics
|
| 673 |
+
performance_comparison = {}
|
| 674 |
+
volatility_comparison = {}
|
| 675 |
+
valuation_comparison = {}
|
| 676 |
+
|
| 677 |
+
for symbol, data in comparison_data.items():
|
| 678 |
+
# Performance (price change %)
|
| 679 |
+
performance_comparison[symbol] = data["current_data"]["change_percent"]
|
| 680 |
+
|
| 681 |
+
# Volatility
|
| 682 |
+
vol = data["technical_analysis"]["volatility_annual"]
|
| 683 |
+
if vol:
|
| 684 |
+
volatility_comparison[symbol] = vol
|
| 685 |
+
|
| 686 |
+
# PE Ratio for valuation
|
| 687 |
+
pe = data["fundamental_metrics"]["pe_ratio"]
|
| 688 |
+
if pe:
|
| 689 |
+
valuation_comparison[symbol] = pe
|
| 690 |
+
|
| 691 |
+
# Find best/worst performers
|
| 692 |
+
best_performer = max(performance_comparison.items(), key=lambda x: x[1]) if performance_comparison else None
|
| 693 |
+
worst_performer = min(performance_comparison.items(), key=lambda x: x[1]) if performance_comparison else None
|
| 694 |
+
|
| 695 |
+
return {
|
| 696 |
+
"symbols_analyzed": list(comparison_data.keys()),
|
| 697 |
+
"comparison_summary": {
|
| 698 |
+
"best_performer": best_performer,
|
| 699 |
+
"worst_performer": worst_performer,
|
| 700 |
+
"avg_performance": round(np.mean(list(performance_comparison.values())), 2) if performance_comparison else None,
|
| 701 |
+
"avg_volatility": round(np.mean(list(volatility_comparison.values())), 2) if volatility_comparison else None
|
| 702 |
+
},
|
| 703 |
+
"detailed_data": comparison_data,
|
| 704 |
+
"analysis_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 705 |
+
}
|
| 706 |
+
|
| 707 |
+
except Exception as e:
|
| 708 |
+
return {"error": f"Failed to compare stocks: {str(e)}"}
|
| 709 |
+
|
| 710 |
+
def _resolve_symbol(self, input_symbol: str) -> str:
|
| 711 |
+
"""Convert company name to stock symbol if needed."""
|
| 712 |
+
input_lower = input_symbol.lower().strip()
|
| 713 |
+
|
| 714 |
+
# Check if it's already a valid symbol format (all caps, 1-5 characters)
|
| 715 |
+
if input_symbol.isupper() and 1 <= len(input_symbol) <= 5:
|
| 716 |
+
return input_symbol
|
| 717 |
+
|
| 718 |
+
# Check common symbols mapping
|
| 719 |
+
if input_lower in self.common_symbols:
|
| 720 |
+
return self.common_symbols[input_lower]
|
| 721 |
+
|
| 722 |
+
# Check if it contains a company name
|
| 723 |
+
for name, symbol in self.common_symbols.items():
|
| 724 |
+
if name in input_lower or input_lower in name:
|
| 725 |
+
return symbol
|
| 726 |
+
|
| 727 |
+
# If not found, assume it's a symbol and convert to uppercase
|
| 728 |
+
return input_symbol.upper()
|
| 729 |
+
|
| 730 |
+
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> float:
|
| 731 |
+
"""Calculate Relative Strength Index."""
|
| 732 |
+
try:
|
| 733 |
+
delta = prices.diff()
|
| 734 |
+
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
| 735 |
+
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
| 736 |
+
rs = gain / loss
|
| 737 |
+
rsi = 100 - (100 / (1 + rs))
|
| 738 |
+
return rsi.iloc[-1]
|
| 739 |
+
except:
|
| 740 |
+
return None
|
| 741 |
+
|
| 742 |
+
def _determine_trend(self, current_price: float, ma_50: float, ma_200: float) -> str:
|
| 743 |
+
"""Determine price trend based on moving averages."""
|
| 744 |
+
try:
|
| 745 |
+
if not ma_50 or not ma_200:
|
| 746 |
+
return "Unknown"
|
| 747 |
+
|
| 748 |
+
if current_price > ma_50 > ma_200:
|
| 749 |
+
return "Strong Uptrend"
|
| 750 |
+
elif current_price > ma_50 and ma_50 < ma_200:
|
| 751 |
+
return "Weak Uptrend"
|
| 752 |
+
elif current_price < ma_50 < ma_200:
|
| 753 |
+
return "Strong Downtrend"
|
| 754 |
+
elif current_price < ma_50 and ma_50 > ma_200:
|
| 755 |
+
return "Weak Downtrend"
|
| 756 |
+
else:
|
| 757 |
+
return "Sideways"
|
| 758 |
+
except:
|
| 759 |
+
return "Unknown"
|
| 760 |
+
|
| 761 |
+
def _generate_investment_analysis(self, price: float, ma_50: float, ma_200: float,
|
| 762 |
+
rsi: float, pe: float, volatility: float, info: dict) -> str:
|
| 763 |
+
"""Generate basic investment analysis summary."""
|
| 764 |
+
try:
|
| 765 |
+
analysis_points = []
|
| 766 |
+
|
| 767 |
+
# Technical analysis
|
| 768 |
+
if rsi:
|
| 769 |
+
if rsi > 70:
|
| 770 |
+
analysis_points.append("Technically overbought (RSI > 70)")
|
| 771 |
+
elif rsi < 30:
|
| 772 |
+
analysis_points.append("Technically oversold (RSI < 30)")
|
| 773 |
+
else:
|
| 774 |
+
analysis_points.append("RSI in neutral range")
|
| 775 |
+
|
| 776 |
+
# Trend analysis
|
| 777 |
+
if ma_50 and ma_200:
|
| 778 |
+
if price > ma_50 > ma_200:
|
| 779 |
+
analysis_points.append("Strong upward trend")
|
| 780 |
+
elif price < ma_50 < ma_200:
|
| 781 |
+
analysis_points.append("Strong downward trend")
|
| 782 |
+
|
| 783 |
+
# Valuation
|
| 784 |
+
if pe:
|
| 785 |
+
if pe > 25:
|
| 786 |
+
analysis_points.append("High P/E ratio - potentially overvalued")
|
| 787 |
+
elif pe < 15:
|
| 788 |
+
analysis_points.append("Low P/E ratio - potentially undervalued")
|
| 789 |
+
else:
|
| 790 |
+
analysis_points.append("Moderate P/E ratio")
|
| 791 |
+
|
| 792 |
+
# Volatility
|
| 793 |
+
if volatility:
|
| 794 |
+
if volatility > 0.4:
|
| 795 |
+
analysis_points.append("High volatility - risky investment")
|
| 796 |
+
elif volatility < 0.2:
|
| 797 |
+
analysis_points.append("Low volatility - stable investment")
|
| 798 |
+
|
| 799 |
+
return " | ".join(analysis_points) if analysis_points else "Analysis unavailable"
|
| 800 |
+
|
| 801 |
+
except:
|
| 802 |
+
return "Analysis unavailable"
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
# Export the class
|
| 806 |
+
__all__ = ['FinancialDataIntegrator']
|